diff --git a/.env.example b/.env.example index 1891a2347..ccc490f1b 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,9 @@ CREATE_FIRST_USER=true # set to false if you want to create the first user manua # login: admin@quivr.app # password: admin +#LOCAL +#OLLAMA_API_BASE_URL=http://host.docker.internal:11434 # Uncomment to activate ollama. This is the local url for the ollama api + @@ -39,9 +42,6 @@ GOOGLE_CLOUD_PROJECT= CELERY_BROKER_URL=redis://redis:6379/0 CELEBRY_BROKER_QUEUE_NAME=quivr-preview.fifo -#LOCAL -#OLLAMA_API_BASE_URL=http://host.docker.internal:11434 # local all in one remove comment to use local llm with Ollama - #RESEND RESEND_API_KEY= diff --git a/README.md b/README.md index d3dc8e742..cc83ef43c 100644 --- a/README.md +++ b/README.md @@ -70,16 +70,18 @@ Ensure you have the following installed: > Want to use [Ollama.ai](https://ollama.ai) instead? - > Finish installing Quivr, put a randon open_api_key in the `.env` file and - > follow the instructions [here](https://brain.quivr.app/docs/Developers/selfHosted/run_fully_local) to update Quivr to use Ollama AFTER you have finished installing Quivr. + > Uncomment the following lines in the `.env` file: + > OLLAMA_API_BASE_URL + > Run the following command to start Ollama: `ollama run llama2` + > You can find more information about Ollama [here](https://ollama.ai/). - **Step 4**: Launch the project ```bash docker compose pull - docker compose up --build - ``` - + docker compose up --build # if OPENAI + # docker compose -f docker-compose-ollama.yml up --build # Only if using Ollama. You need to run `ollama run llama2` first. +``` - **Step 5**: Login to the app diff --git a/docker-compose-ollama.yml b/docker-compose-ollama.yml new file mode 100644 index 000000000..a45c59726 --- /dev/null +++ b/docker-compose-ollama.yml @@ -0,0 +1,505 @@ +version: "3.8" + +services: + frontend: + pull_policy: never + build: + context: frontend + dockerfile: Dockerfile + args: + - NEXT_PUBLIC_ENV=local + - NEXT_PUBLIC_BACKEND_URL=${NEXT_PUBLIC_BACKEND_URL} + - NEXT_PUBLIC_SUPABASE_URL=${NEXT_PUBLIC_SUPABASE_URL} + - NEXT_PUBLIC_SUPABASE_ANON_KEY=${NEXT_PUBLIC_SUPABASE_ANON_KEY} + - NEXT_PUBLIC_CMS_URL=${NEXT_PUBLIC_CMS_URL} + - NEXT_PUBLIC_FRONTEND_URL=${NEXT_PUBLIC_FRONTEND_URL} + container_name: web + depends_on: + - backend-core + restart: always + ports: + - 3000:3000 + + + backend-core: + image: backend-base + pull_policy: never + env_file: + - .env + build: + context: backend + dockerfile: Dockerfile + container_name: backend-core + command: + - "uvicorn" + - "main:app" + - "--host" + - "0.0.0.0" + - "--port" + - "5050" + - "--workers" + - "1" + restart: always + depends_on: + db: + condition: service_healthy + kong: + condition: service_healthy + ports: + - 5050:5050 + + redis: + image: redis:latest + container_name: redis + restart: always + ports: + - 6379:6379 + + worker: + pull_policy: never + image: backend-base + env_file: + - .env + build: + context: backend + dockerfile: Dockerfile + container_name: worker + command: celery -A celery_worker worker -l info + restart: always + depends_on: + - redis + + beat: + image: backend-base + pull_policy: never + env_file: + - .env + build: + context: backend + dockerfile: Dockerfile + container_name: beat + command: celery -A celery_worker beat -l info + restart: always + depends_on: + - redis + + flower: + image: backend-base + pull_policy: never + env_file: + - .env + build: + context: backend + dockerfile: Dockerfile + container_name: flower + command: celery -A celery_worker flower -l info --port=5555 + restart: always + depends_on: + - redis + - worker + - beat + ports: + - 5555:5555 + + studio: + container_name: supabase-studio + image: supabase/studio:20231123-64a766a + restart: unless-stopped + healthcheck: + test: + [ + "CMD", + "node", + "-e", + "require('http').get('http://localhost:3000/api/profile', (r) => {if (r.statusCode !== 200) throw new Error(r.statusCode)})" + ] + timeout: 5s + interval: 5s + retries: 3 + depends_on: + analytics: + condition: service_healthy + environment: + STUDIO_PG_META_URL: http://meta:8080 + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + + DEFAULT_ORGANIZATION_NAME: ${STUDIO_DEFAULT_ORGANIZATION} + DEFAULT_PROJECT_NAME: ${STUDIO_DEFAULT_PROJECT} + + SUPABASE_URL: http://kong:8000 + SUPABASE_PUBLIC_URL: ${SUPABASE_PUBLIC_URL} + SUPABASE_ANON_KEY: ${ANON_KEY} + SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY} + + LOGFLARE_API_KEY: ${LOGFLARE_API_KEY} + LOGFLARE_URL: http://analytics:4000 + NEXT_PUBLIC_ENABLE_LOGS: true + # Comment to use Big Query backend for analytics + NEXT_ANALYTICS_BACKEND_PROVIDER: postgres + # Uncomment to use Big Query backend for analytics + # NEXT_ANALYTICS_BACKEND_PROVIDER: bigquery + + kong: + container_name: supabase-kong + image: kong:2.8.1 + restart: unless-stopped + # https://unix.stackexchange.com/a/294837 + entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start' + ports: + - ${KONG_HTTP_PORT}:8000/tcp + - ${KONG_HTTPS_PORT}:8443/tcp + depends_on: + analytics: + condition: service_healthy + environment: + KONG_DATABASE: "off" + KONG_DECLARATIVE_CONFIG: /home/kong/kong.yml + # https://github.com/supabase/cli/issues/14 + KONG_DNS_ORDER: LAST,A,CNAME + KONG_PLUGINS: request-transformer,cors,key-auth,acl,basic-auth + KONG_NGINX_PROXY_PROXY_BUFFER_SIZE: 160k + KONG_NGINX_PROXY_PROXY_BUFFERS: 64 160k + SUPABASE_ANON_KEY: ${ANON_KEY} + SUPABASE_SERVICE_KEY: ${SERVICE_ROLE_KEY} + DASHBOARD_USERNAME: ${DASHBOARD_USERNAME} + DASHBOARD_PASSWORD: ${DASHBOARD_PASSWORD} + volumes: + # https://github.com/supabase/supabase/issues/12661 + - ./volumes/api/kong.yml:/home/kong/temp.yml:ro + + auth: + container_name: supabase-auth + image: supabase/gotrue:v2.99.0 + depends_on: + db: + # Disable this if you are using an external Postgres database + condition: service_healthy + analytics: + condition: service_healthy + healthcheck: + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://localhost:9999/health" + ] + timeout: 5s + interval: 5s + retries: 3 + restart: unless-stopped + environment: + GOTRUE_API_HOST: 0.0.0.0 + GOTRUE_API_PORT: 9999 + API_EXTERNAL_URL: ${API_EXTERNAL_URL} + + GOTRUE_DB_DRIVER: postgres + GOTRUE_DB_DATABASE_URL: postgres://supabase_auth_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + + GOTRUE_SITE_URL: ${SITE_URL} + GOTRUE_URI_ALLOW_LIST: ${ADDITIONAL_REDIRECT_URLS} + GOTRUE_DISABLE_SIGNUP: ${DISABLE_SIGNUP} + + GOTRUE_JWT_ADMIN_ROLES: service_role + GOTRUE_JWT_AUD: authenticated + GOTRUE_JWT_DEFAULT_GROUP_NAME: authenticated + GOTRUE_JWT_EXP: ${JWT_EXPIRY} + GOTRUE_JWT_SECRET: ${JWT_SECRET} + + GOTRUE_EXTERNAL_EMAIL_ENABLED: ${ENABLE_EMAIL_SIGNUP} + GOTRUE_MAILER_AUTOCONFIRM: ${ENABLE_EMAIL_AUTOCONFIRM} + # GOTRUE_MAILER_SECURE_EMAIL_CHANGE_ENABLED: true + # GOTRUE_SMTP_MAX_FREQUENCY: 1s + GOTRUE_SMTP_ADMIN_EMAIL: ${SMTP_ADMIN_EMAIL} + GOTRUE_SMTP_HOST: ${SMTP_HOST} + GOTRUE_SMTP_PORT: ${SMTP_PORT} + GOTRUE_SMTP_USER: ${SMTP_USER} + GOTRUE_SMTP_PASS: ${SMTP_PASS} + GOTRUE_SMTP_SENDER_NAME: ${SMTP_SENDER_NAME} + GOTRUE_MAILER_URLPATHS_INVITE: ${MAILER_URLPATHS_INVITE} + GOTRUE_MAILER_URLPATHS_CONFIRMATION: ${MAILER_URLPATHS_CONFIRMATION} + GOTRUE_MAILER_URLPATHS_RECOVERY: ${MAILER_URLPATHS_RECOVERY} + GOTRUE_MAILER_URLPATHS_EMAIL_CHANGE: ${MAILER_URLPATHS_EMAIL_CHANGE} + + GOTRUE_EXTERNAL_PHONE_ENABLED: ${ENABLE_PHONE_SIGNUP} + GOTRUE_SMS_AUTOCONFIRM: ${ENABLE_PHONE_AUTOCONFIRM} + + rest: + container_name: supabase-rest + image: postgrest/postgrest:v11.2.2 + depends_on: + db: + # Disable this if you are using an external Postgres database + condition: service_healthy + analytics: + condition: service_healthy + restart: unless-stopped + environment: + PGRST_DB_URI: postgres://authenticator:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + PGRST_DB_SCHEMAS: ${PGRST_DB_SCHEMAS} + PGRST_DB_ANON_ROLE: anon + PGRST_JWT_SECRET: ${JWT_SECRET} + PGRST_DB_USE_LEGACY_GUCS: "false" + PGRST_APP_SETTINGS_JWT_SECRET: ${JWT_SECRET} + PGRST_APP_SETTINGS_JWT_EXP: ${JWT_EXPIRY} + command: "postgrest" + + realtime: + # This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain + container_name: realtime-dev.supabase-realtime + image: supabase/realtime:v2.25.35 + depends_on: + db: + # Disable this if you are using an external Postgres database + condition: service_healthy + analytics: + condition: service_healthy + healthcheck: + test: + [ + "CMD", + "bash", + "-c", + "printf \\0 > /dev/tcp/localhost/4000" + ] + timeout: 5s + interval: 5s + retries: 3 + restart: unless-stopped + environment: + PORT: 4000 + DB_HOST: ${POSTGRES_HOST} + DB_PORT: ${POSTGRES_PORT} + DB_USER: supabase_admin + DB_PASSWORD: ${POSTGRES_PASSWORD} + DB_NAME: ${POSTGRES_DB} + DB_AFTER_CONNECT_QUERY: 'SET search_path TO _realtime' + DB_ENC_KEY: supabaserealtime + API_JWT_SECRET: ${JWT_SECRET} + FLY_ALLOC_ID: fly123 + FLY_APP_NAME: realtime + SECRET_KEY_BASE: UpNVntn3cDxHJpq99YMc1T1AQgQpc8kfYTuRgBiYa15BLrx8etQoXz3gZv1/u2oq + ERL_AFLAGS: -proto_dist inet_tcp + ENABLE_TAILSCALE: "false" + DNS_NODES: "''" + command: > + sh -c "/app/bin/migrate && /app/bin/realtime eval 'Realtime.Release.seeds(Realtime.Repo)' && /app/bin/server" + + storage: + container_name: supabase-storage + image: supabase/storage-api:v0.43.11 + depends_on: + db: + # Disable this if you are using an external Postgres database + condition: service_healthy + rest: + condition: service_started + imgproxy: + condition: service_started + healthcheck: + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://localhost:5000/status" + ] + timeout: 5s + interval: 5s + retries: 3 + restart: unless-stopped + environment: + ANON_KEY: ${ANON_KEY} + SERVICE_KEY: ${SERVICE_ROLE_KEY} + POSTGREST_URL: http://rest:3000 + PGRST_JWT_SECRET: ${JWT_SECRET} + DATABASE_URL: postgres://supabase_storage_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + FILE_SIZE_LIMIT: 52428800 + STORAGE_BACKEND: file + FILE_STORAGE_BACKEND_PATH: /var/lib/storage + TENANT_ID: stub + # TODO: https://github.com/supabase/storage-api/issues/55 + REGION: stub + GLOBAL_S3_BUCKET: stub + ENABLE_IMAGE_TRANSFORMATION: "true" + IMGPROXY_URL: http://imgproxy:5001 + volumes: + - ./volumes/storage:/var/lib/storage:z + + imgproxy: + container_name: supabase-imgproxy + image: darthsim/imgproxy:v3.8.0 + healthcheck: + test: [ "CMD", "imgproxy", "health" ] + timeout: 5s + interval: 5s + retries: 3 + environment: + IMGPROXY_BIND: ":5001" + IMGPROXY_LOCAL_FILESYSTEM_ROOT: / + IMGPROXY_USE_ETAG: "true" + IMGPROXY_ENABLE_WEBP_DETECTION: ${IMGPROXY_ENABLE_WEBP_DETECTION} + volumes: + - ./volumes/storage:/var/lib/storage:z + + meta: + container_name: supabase-meta + image: supabase/postgres-meta:v0.68.0 + depends_on: + db: + # Disable this if you are using an external Postgres database + condition: service_healthy + analytics: + condition: service_healthy + restart: unless-stopped + environment: + PG_META_PORT: 8080 + PG_META_DB_HOST: ${POSTGRES_HOST} + PG_META_DB_PORT: ${POSTGRES_PORT} + PG_META_DB_NAME: ${POSTGRES_DB} + PG_META_DB_USER: supabase_admin + PG_META_DB_PASSWORD: ${POSTGRES_PASSWORD} + + functions: + container_name: supabase-edge-functions + image: supabase/edge-runtime:v1.22.4 + restart: unless-stopped + depends_on: + analytics: + condition: service_healthy + environment: + JWT_SECRET: ${JWT_SECRET} + SUPABASE_URL: http://kong:8000 + SUPABASE_ANON_KEY: ${ANON_KEY} + SUPABASE_SERVICE_ROLE_KEY: ${SERVICE_ROLE_KEY} + SUPABASE_DB_URL: postgresql://postgres:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + # TODO: Allow configuring VERIFY_JWT per function. This PR might help: https://github.com/supabase/cli/pull/786 + VERIFY_JWT: "${FUNCTIONS_VERIFY_JWT}" + volumes: + - ./volumes/functions:/home/deno/functions:Z + command: + - start + - --main-service + - /home/deno/functions/main + + analytics: + container_name: supabase-analytics + image: supabase/logflare:1.4.0 + healthcheck: + test: [ "CMD", "curl", "http://localhost:4000/health" ] + timeout: 5s + interval: 5s + retries: 10 + restart: unless-stopped + depends_on: + db: + # Disable this if you are using an external Postgres database + condition: service_healthy + # Uncomment to use Big Query backend for analytics + # volumes: + # - type: bind + # source: ${PWD}/gcloud.json + # target: /opt/app/rel/logflare/bin/gcloud.json + # read_only: true + environment: + LOGFLARE_NODE_HOST: 127.0.0.1 + DB_USERNAME: supabase_admin + DB_DATABASE: ${POSTGRES_DB} + DB_HOSTNAME: ${POSTGRES_HOST} + DB_PORT: ${POSTGRES_PORT} + DB_PASSWORD: ${POSTGRES_PASSWORD} + DB_SCHEMA: _analytics + LOGFLARE_API_KEY: ${LOGFLARE_API_KEY} + LOGFLARE_SINGLE_TENANT: true + LOGFLARE_SUPABASE_MODE: true + LOGFLARE_MIN_CLUSTER_SIZE: 1 + RELEASE_COOKIE: cookie + + # Comment variables to use Big Query backend for analytics + POSTGRES_BACKEND_URL: postgresql://supabase_admin:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB} + POSTGRES_BACKEND_SCHEMA: _analytics + LOGFLARE_FEATURE_FLAG_OVERRIDE: multibackend=true + + # Uncomment to use Big Query backend for analytics + # GOOGLE_PROJECT_ID: ${GOOGLE_PROJECT_ID} + # GOOGLE_PROJECT_NUMBER: ${GOOGLE_PROJECT_NUMBER} + ports: + - 4000:4000 + entrypoint: | + sh -c `cat <<'EOF' > run.sh && sh run.sh + ./logflare eval Logflare.Release.migrate + ./logflare start --sname logflare + EOF + ` + + # Comment out everything below this point if you are using an external Postgres database + db: + container_name: supabase-db + image: supabase/postgres:15.1.0.117 + healthcheck: + test: pg_isready -U postgres -h localhost + interval: 5s + timeout: 5s + retries: 10 + depends_on: + vector: + condition: service_healthy + command: + - postgres + - -c + - config_file=/etc/postgresql/postgresql.conf + - -c + - log_min_messages=fatal # prevents Realtime polling queries from appearing in logs + restart: unless-stopped + ports: + # Pass down internal port because it's set dynamically by other services + - ${POSTGRES_PORT}:${POSTGRES_PORT} + environment: + POSTGRES_HOST: /var/run/postgresql + PGPORT: ${POSTGRES_PORT} + POSTGRES_PORT: ${POSTGRES_PORT} + PGPASSWORD: ${POSTGRES_PASSWORD} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + PGDATABASE: ${POSTGRES_DB} + POSTGRES_DB: ${POSTGRES_DB} + JWT_SECRET: ${JWT_SECRET} + JWT_EXP: ${JWT_EXPIRY} + volumes: + - ./volumes/db/realtime.sql:/docker-entrypoint-initdb.d/migrations/99-realtime.sql:Z + # Must be superuser to create event trigger + - ./volumes/db/webhooks.sql:/docker-entrypoint-initdb.d/init-scripts/98-webhooks.sql:Z + # Must be superuser to alter reserved role + - ./volumes/db/roles.sql:/docker-entrypoint-initdb.d/init-scripts/99-roles.sql:Z + # Initialize the database settings with JWT_SECRET and JWT_EXP + - ./volumes/db/jwt.sql:/docker-entrypoint-initdb.d/init-scripts/99-jwt.sql:Z + # PGDATA directory is persisted between restarts + - ./volumes/db/data:/var/lib/postgresql/data:Z + # Changes required for Analytics support + - ./volumes/db/logs.sql:/docker-entrypoint-initdb.d/migrations/99-logs.sql:Z + - ./scripts/tables-ollama.sql:/docker-entrypoint-initdb.d/seed.sql + + vector: + container_name: supabase-vector + image: timberio/vector:0.28.1-alpine + healthcheck: + test: + [ + "CMD", + "wget", + "--no-verbose", + "--tries=1", + "--spider", + "http://vector:9001/health" + ] + timeout: 5s + interval: 5s + retries: 3 + volumes: + - ./volumes/logs/vector.yml:/etc/vector/vector.yml:ro + - ${DOCKER_SOCKET_LOCATION}:/var/run/docker.sock:ro + + command: [ "--config", "etc/vector/vector.yml" ] \ No newline at end of file diff --git a/docs/docs/Developers/selfHosted/run_fully_local.md b/docs/docs/Developers/selfHosted/run_fully_local.md index 11ca23b88..b79bea913 100644 --- a/docs/docs/Developers/selfHosted/run_fully_local.md +++ b/docs/docs/Developers/selfHosted/run_fully_local.md @@ -31,73 +31,19 @@ Then run the following command to run Ollama in the background: ollama run llama2 ``` -### Update Quivr to use Ollama - -In order to have Quivr use Ollama we need to update the tables in Supabase to support the embedding format that Ollama uses. Ollama uses by default llama 2 that produces 4096 dimensional embeddings while OpenAI API produces 1536 dimensional embeddings. - - -Go to your supabase localhost instance [http://localhost:8000](http://localhost:8000) (default password admin/admin) and delete your table `vectors` and create a new table vectors with the following schema: - -```sql -DROP TABLE IF EXISTS vectors; -CREATE TABLE IF NOT EXISTS vectors ( - id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, - content TEXT, - file_sha1 TEXT, - metadata JSONB, - embedding VECTOR(4096) -); -``` - -Then run the following command to update the table: - -```sql -CREATE OR REPLACE FUNCTION match_vectors(query_embedding VECTOR(4096), match_count INT, p_brain_id UUID) -RETURNS TABLE( - id UUID, - brain_id UUID, - content TEXT, - metadata JSONB, - embedding VECTOR(4096), - similarity FLOAT -) LANGUAGE plpgsql AS $$ -#variable_conflict use_column -BEGIN - RETURN QUERY - SELECT - vectors.id, - brains_vectors.brain_id, - vectors.content, - vectors.metadata, - vectors.embedding, - 1 - (vectors.embedding <=> query_embedding) AS similarity - FROM - vectors - INNER JOIN - brains_vectors ON vectors.id = brains_vectors.vector_id - WHERE brains_vectors.brain_id = p_brain_id - ORDER BY - vectors.embedding <=> query_embedding - LIMIT match_count; -END; -$$; -``` - -This will update the match_vectors function to use the new embedding format. - ## Add Ollama Model to Quivr Now that you have your model running locally, you need to add it to Quivr. -In order to allow the user to choose between the OpenAI API and Ollama, we need to add a new model to the Quivr backend. +In order to allow the user to choose between the Ollama, we need to add a new model to the Quivr backend. Go to supabase and in the table `user_settings` either add by default or to your user the following value to the `models` column: ```json [ - "gpt-3.5-turbo-1106", - "ollama/llama2" + "ollama/llama2", + "ollama/mistral", ] ``` @@ -108,17 +54,3 @@ By adding this as default, it means that all new users will have this model by d ```sql DELETE TABLE user_settings; ``` - - -## Env Variables - - -In order to have Quivr use Ollama we need to update the env variables. - -Go to `.env` and un comment the following env variables: - -```bash -OLLAMA_API_BASE_URL=http://host.docker.internal:11434 -``` - -Then go to the Quivr and you are good to go. \ No newline at end of file diff --git a/scripts/tables-ollama.sql b/scripts/tables-ollama.sql new file mode 100644 index 000000000..ea57b109c --- /dev/null +++ b/scripts/tables-ollama.sql @@ -0,0 +1,449 @@ +-- Create users table +CREATE TABLE IF NOT EXISTS user_daily_usage( + user_id UUID REFERENCES auth.users (id), + email TEXT, + date TEXT, + daily_requests_count INT, + PRIMARY KEY (user_id, date) +); + +-- Create chats table +CREATE TABLE IF NOT EXISTS chats( + chat_id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + user_id UUID REFERENCES auth.users (id), + creation_time TIMESTAMP DEFAULT current_timestamp, + history JSONB, + chat_name TEXT +); + + +-- Create vector extension +CREATE EXTENSION IF NOT EXISTS vector; + +-- Create vectors table +CREATE TABLE IF NOT EXISTS vectors ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + content TEXT, + file_sha1 TEXT, + metadata JSONB, + embedding VECTOR(4096) +); + +-- Create function to match vectors +CREATE OR REPLACE FUNCTION match_vectors(query_embedding VECTOR(4096), match_count INT, p_brain_id UUID) +RETURNS TABLE( + id UUID, + brain_id UUID, + content TEXT, + metadata JSONB, + embedding VECTOR(4096), + similarity FLOAT +) LANGUAGE plpgsql AS $$ +#variable_conflict use_column +BEGIN + RETURN QUERY + SELECT + vectors.id, + brains_vectors.brain_id, + vectors.content, + vectors.metadata, + vectors.embedding, + 1 - (vectors.embedding <=> query_embedding) AS similarity + FROM + vectors + INNER JOIN + brains_vectors ON vectors.id = brains_vectors.vector_id + WHERE brains_vectors.brain_id = p_brain_id + ORDER BY + vectors.embedding <=> query_embedding + LIMIT match_count; +END; +$$; + +-- Create stats table +CREATE TABLE IF NOT EXISTS stats ( + time TIMESTAMP, + chat BOOLEAN, + embedding BOOLEAN, + details TEXT, + metadata JSONB, + id INTEGER PRIMARY KEY GENERATED ALWAYS AS IDENTITY +); + +-- Create summaries table +CREATE TABLE IF NOT EXISTS summaries ( + id BIGSERIAL PRIMARY KEY, + document_id UUID REFERENCES vectors(id), + content TEXT, + metadata JSONB, + embedding VECTOR(4096) +); + +-- Create function to match summaries +CREATE OR REPLACE FUNCTION match_summaries(query_embedding VECTOR(4096), match_count INT, match_threshold FLOAT) +RETURNS TABLE( + id BIGINT, + document_id UUID, + content TEXT, + metadata JSONB, + embedding VECTOR(4096), + similarity FLOAT +) LANGUAGE plpgsql AS $$ +#variable_conflict use_column +BEGIN + RETURN QUERY + SELECT + id, + document_id, + content, + metadata, + embedding, + 1 - (summaries.embedding <=> query_embedding) AS similarity + FROM + summaries + WHERE 1 - (summaries.embedding <=> query_embedding) > match_threshold + ORDER BY + summaries.embedding <=> query_embedding + LIMIT match_count; +END; +$$; + +-- Create api_keys table +CREATE TABLE IF NOT EXISTS api_keys( + key_id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + user_id UUID REFERENCES auth.users (id), + api_key TEXT UNIQUE, + creation_time TIMESTAMP DEFAULT current_timestamp, + deleted_time TIMESTAMP, + is_active BOOLEAN DEFAULT true +); + +--- Create prompts table +CREATE TABLE IF NOT EXISTS prompts ( + id UUID DEFAULT uuid_generate_v4() PRIMARY KEY, + title VARCHAR(255), + content TEXT, + status VARCHAR(255) DEFAULT 'private' +); + +DO $$ +BEGIN +IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'brain_type_enum') THEN + -- Create the ENUM type 'brain_type' if it doesn't exist + CREATE TYPE brain_type_enum AS ENUM ('doc', 'api'); +END IF; +END $$; + +--- Create brains table +CREATE TABLE IF NOT EXISTS brains ( + brain_id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + name TEXT NOT NULL, + status TEXT, + description TEXT, + model TEXT, + max_tokens INT, + temperature FLOAT, + prompt_id UUID REFERENCES prompts(id), + last_update TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + brain_type brain_type_enum DEFAULT 'doc' +); + + +-- Create chat_history table +CREATE TABLE IF NOT EXISTS chat_history ( + message_id UUID DEFAULT uuid_generate_v4(), + chat_id UUID REFERENCES chats(chat_id), + user_message TEXT, + assistant TEXT, + message_time TIMESTAMP DEFAULT current_timestamp, + PRIMARY KEY (chat_id, message_id), + prompt_id UUID REFERENCES prompts(id), + brain_id UUID REFERENCES brains(brain_id) +); + +-- Create notification table + +CREATE TABLE IF NOT EXISTS notifications ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + datetime TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + chat_id UUID REFERENCES chats(chat_id), + message TEXT, + action VARCHAR(255) NOT NULL, + status VARCHAR(255) NOT NULL +); + + +-- Create brains X users table +CREATE TABLE IF NOT EXISTS brains_users ( + brain_id UUID, + user_id UUID, + rights VARCHAR(255), + default_brain BOOLEAN DEFAULT false, + PRIMARY KEY (brain_id, user_id), + FOREIGN KEY (user_id) REFERENCES auth.users (id), + FOREIGN KEY (brain_id) REFERENCES brains (brain_id) +); + +-- Create brains X vectors table +CREATE TABLE IF NOT EXISTS brains_vectors ( + brain_id UUID, + vector_id UUID, + file_sha1 TEXT, + PRIMARY KEY (brain_id, vector_id), + FOREIGN KEY (vector_id) REFERENCES vectors (id), + FOREIGN KEY (brain_id) REFERENCES brains (brain_id) +); + +-- Create brains X vectors table +CREATE TABLE IF NOT EXISTS brain_subscription_invitations ( + brain_id UUID, + email VARCHAR(255), + rights VARCHAR(255), + PRIMARY KEY (brain_id, email), + FOREIGN KEY (brain_id) REFERENCES brains (brain_id) +); + +--- Create user_identity table +CREATE TABLE IF NOT EXISTS user_identity ( + user_id UUID PRIMARY KEY, + openai_api_key VARCHAR(255) +); + +-- Create the new table with 6 columns +CREATE TABLE IF NOT EXISTS api_brain_definition ( + brain_id UUID REFERENCES brains(brain_id), + method VARCHAR(255) CHECK (method IN ('GET', 'POST', 'PUT', 'DELETE')), + url VARCHAR(255), + params JSON, + search_params JSON, + secrets JSON +); + +CREATE OR REPLACE FUNCTION public.get_user_email_by_user_id(user_id uuid) +RETURNS TABLE (email text) +SECURITY definer +AS $$ +BEGIN + RETURN QUERY SELECT au.email::text FROM auth.users au WHERE au.id = user_id; +END; +$$ LANGUAGE plpgsql; + +CREATE OR REPLACE FUNCTION public.get_user_id_by_user_email(user_email text) +RETURNS TABLE (user_id uuid) +SECURITY DEFINER +AS $$ +BEGIN + RETURN QUERY SELECT au.id::uuid FROM auth.users au WHERE au.email = user_email; +END; +$$ LANGUAGE plpgsql; + + + +CREATE TABLE IF NOT EXISTS migrations ( + name VARCHAR(255) PRIMARY KEY, + executed_at TIMESTAMPTZ DEFAULT current_timestamp +); + +CREATE TABLE IF NOT EXISTS user_settings ( + user_id UUID PRIMARY KEY, + models JSONB DEFAULT '["ollama/llama2"]'::jsonb, + daily_chat_credit INT DEFAULT 300, + max_brains INT DEFAULT 30, + max_brain_size INT DEFAULT 100000000 +); + +-- knowledge table +CREATE TABLE IF NOT EXISTS knowledge ( + id UUID DEFAULT gen_random_uuid() PRIMARY KEY, + file_name TEXT, + url TEXT, + brain_id UUID NOT NULL REFERENCES brains(brain_id), + extension TEXT NOT NULL, + CHECK ((file_name IS NOT NULL AND url IS NULL) OR (file_name IS NULL AND url IS NOT NULL)) +); + + +-- knowledge_vectors table +CREATE TABLE IF NOT EXISTS knowledge_vectors ( + knowledge_id UUID NOT NULL REFERENCES knowledge(id), + vector_id UUID NOT NULL REFERENCES vectors(id), + embedding_model TEXT NOT NULL, + PRIMARY KEY (knowledge_id, vector_id, embedding_model) +); + +-- Create the function to add user_id to the onboardings table +CREATE OR REPLACE FUNCTION public.create_user_onboarding() RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.onboardings (user_id) + VALUES (NEW.id); + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY definer; + +-- Revoke all on function handle_new_user_onboarding() from PUBLIC; +REVOKE ALL ON FUNCTION create_user_onboarding() FROM PUBLIC; + +-- Drop the trigger if it exists +DROP TRIGGER IF EXISTS create_user_onboarding_trigger ON auth.users; + +-- Create the trigger on the insert into the auth.users table +CREATE TRIGGER create_user_onboarding_trigger +AFTER INSERT ON auth.users +FOR EACH ROW +EXECUTE FUNCTION public.create_user_onboarding(); + +-- Create the onboarding table +CREATE TABLE IF NOT EXISTS onboardings ( + user_id UUID NOT NULL REFERENCES auth.users (id), + onboarding_a BOOLEAN NOT NULL DEFAULT true, + onboarding_b1 BOOLEAN NOT NULL DEFAULT true, + onboarding_b2 BOOLEAN NOT NULL DEFAULT true, + onboarding_b3 BOOLEAN NOT NULL DEFAULT true, + creation_time TIMESTAMP DEFAULT current_timestamp, + PRIMARY KEY (user_id) +); + + +-- Stripe settings -- +-- Create extension 'wrappers' if it doesn't exist +CREATE EXTENSION IF NOT EXISTS wrappers; + +-- Create foreign data wrapper 'stripe_wrapper' if it doesn't exist +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.foreign_data_wrappers + WHERE foreign_data_wrapper_name = 'stripe_wrapper' + ) THEN + CREATE FOREIGN DATA WRAPPER stripe_wrapper + HANDLER stripe_fdw_handler; + END IF; +END $$; + +-- Check if the server 'stripe_server' exists before creating it +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_foreign_server WHERE srvname = 'stripe_server') THEN + CREATE SERVER stripe_server + FOREIGN DATA WRAPPER stripe_wrapper + OPTIONS ( + api_key 'your_stripe_api_key' -- Replace with your Stripe API key + ); + END IF; +END $$; + +-- Create foreign table 'public.customers' if it doesn't exist +DO $$ +BEGIN + IF NOT EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_name = 'customers' + ) THEN + CREATE FOREIGN TABLE public.customers ( + id text, + email text, + name text, + description text, + created timestamp, + attrs jsonb + ) + SERVER stripe_server + OPTIONS ( + OBJECT 'customers', + ROWID_COLUMN 'id' + ); + END IF; +END $$; + +-- Create table 'users' if it doesn't exist +CREATE TABLE IF NOT EXISTS public.users ( + id uuid REFERENCES auth.users NOT NULL PRIMARY KEY, + email text +); + +-- Create or replace function 'public.handle_new_user' +CREATE OR REPLACE FUNCTION public.handle_new_user() +RETURNS TRIGGER AS $$ +BEGIN + INSERT INTO public.users (id, email) + VALUES (NEW.id, NEW.email); + RETURN NEW; +END; +$$ LANGUAGE plpgsql SECURITY DEFINER; + +-- Check if the trigger 'on_auth_user_created' exists before creating it +DO $$ +BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_trigger WHERE tgname = 'on_auth_user_created') THEN + CREATE TRIGGER on_auth_user_created + AFTER INSERT ON auth.users + FOR EACH ROW EXECUTE FUNCTION public.handle_new_user(); + END IF; +END $$; + +insert into + storage.buckets (id, name) +values + ('quivr', 'quivr'); + +CREATE POLICY "Access Quivr Storage 1jccrwz_0" ON storage.objects FOR INSERT TO anon WITH CHECK (bucket_id = 'quivr'); + +CREATE POLICY "Access Quivr Storage 1jccrwz_1" ON storage.objects FOR SELECT TO anon USING (bucket_id = 'quivr'); + +CREATE POLICY "Access Quivr Storage 1jccrwz_2" ON storage.objects FOR UPDATE TO anon USING (bucket_id = 'quivr'); + +CREATE POLICY "Access Quivr Storage 1jccrwz_3" ON storage.objects FOR DELETE TO anon USING (bucket_id = 'quivr'); + +-- Create functions for secrets in vault +CREATE OR REPLACE FUNCTION insert_secret(name text, secret text) +returns uuid +language plpgsql +security definer +set search_path = public +as $$ +begin + return vault.create_secret(secret, name); +end; +$$; + + +create or replace function read_secret(secret_name text) +returns text +language plpgsql +security definer set search_path = public +as $$ +declare + secret text; +begin + select decrypted_secret from vault.decrypted_secrets where name = + secret_name into secret; + return secret; +end; +$$; + +create or replace function delete_secret(secret_name text) +returns text +language plpgsql +security definer set search_path = public +as $$ +declare + deleted_rows int; +begin + delete from vault.decrypted_secrets where name = secret_name; + get diagnostics deleted_rows = row_count; + if deleted_rows = 0 then + return false; + else + return true; + end if; +end; +$$; + + +INSERT INTO migrations (name) +SELECT '20231128173900_remove_openai_api_key' +WHERE NOT EXISTS ( + SELECT 1 FROM migrations WHERE name = '20231128173900_remove_openai_api_key' +);