diff --git a/.github/workflows/main-release.yaml b/.github/workflows/main-release.yaml index 90c83ac..b7fffd1 100644 --- a/.github/workflows/main-release.yaml +++ b/.github/workflows/main-release.yaml @@ -8,6 +8,7 @@ on: push: branches: - main + - fork-modifications jobs: build-and-push: @@ -32,13 +33,22 @@ jobs: - name: Expose GH Runtime uses: crazy-max/ghaction-github-runtime@v3 + - name: set lower case owner name + run: | + echo "OWNER_LC=${OWNER,,}" >>${GITHUB_ENV} + env: + OWNER: "${{ github.repository_owner }}" + + - name: Convert repository name to lowercase + run: echo "REPO_NAME_LC=$(echo ${{ github.event.repository.name }} | awk '{print tolower($0)}')" >> $GITHUB_ENV + - name: Build and Push Docker Images run: | make build_and_push_images env: REGISTRY: "ghcr.io" - ORG: ${{ github.repository_owner }} - REPO: ${{ github.event.repository.name }} + ORG: ${{ env.OWNER_LC }} + REPO: ${{ env.REPO_NAME_LC }} GITHUB_WORKFLOW: ${{ github.workflow }} build-tauri: diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml index 3102a57..7411b8a 100644 --- a/.github/workflows/tag-release.yaml +++ b/.github/workflows/tag-release.yaml @@ -30,7 +30,7 @@ jobs: - name: Build and Push Docker Images run: | - TAG=${GITHUB_REF#refs/tags/} make build_and_push_images + TAG=${GITHUB_REF#refs/heads/} make build_and_push_images env: REGISTRY: ghcr.io ORG: ${{ github.repository_owner }} @@ -69,7 +69,7 @@ jobs: - name: get release version id: get_release_version - run: echo "TAG=${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV + run: echo "TAG=${GITHUB_REF#refs/heads/}" >> $GITHUB_ENV - name: get release id id: get_release_id diff --git a/Makefile b/Makefile index d5ad67b..5b6fff5 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ TAG := $(or $(TAG),main) GITHUB_WORKFLOW := $(or $(GITHUB_WORKFLOW),local) REGISTRY := $(or $(REGISTRY),index.docker.io) -PLATFORMS := linux/amd64,linux/arm64 +PLATFORMS := linux/amd64 BUILDX_FLAGS := --platform $(PLATFORMS) --push define get_full_tag diff --git a/core/config/__init__.py b/core/config/__init__.py new file mode 100644 index 0000000..27c9ec6 --- /dev/null +++ b/core/config/__init__.py @@ -0,0 +1 @@ +from .config import * diff --git a/core/config/config.py b/core/config/config.py new file mode 100644 index 0000000..f3d6b09 --- /dev/null +++ b/core/config/config.py @@ -0,0 +1,84 @@ +from os import getenv + +def get_mongo_database_name(): + return getenv("MONGODB_DATABASE", "rubra_db") + +def get_mongo_url() -> str: + url = getenv("MONGODB_URL") + if url: + return url + + host = getenv("MONGODB_HOST", "localhost") + user = getenv("MONGODB_USER", getenv("MONGODB_USERNAME", None)) + password = getenv("MONGODB_PASS", getenv("MONGODB_PASSWORD", None)) + port = getenv("MONGODB_PORT", 27017) + database = get_mongo_database_name() + + if user and not password: + print("MONGODB_USER set but password not found, ignoring user") + + if not user and password: + print("MONGODB_PASSWORD set but user not found, ignoring password") + + if user and password: + return f"mongodb://{user}:{password}@{host}:${port}/{database}" + + return f"mongodb://{host}:{port}/{database}" + +def get_redis_url() -> str: + url = getenv("REDIS_URL") + if url: + return url + + host = getenv("REDIS_HOST", "localhost") + password = getenv("REDIS_PASS", getenv("REDIS_PASSWORD", None)) + user = getenv("REDIS_USER", getenv("REDIS_USERNAME", None)) + port = getenv("REDIS_PORT", 6379) + database = getenv("REDIS_DATABASE", 0) + + if password: + return f"redis://{user or ''}:{password}@{host}:{port}/{database}" + + return f"redis://{host}:{port}/{database}" + +def get_litellm_url() -> str: + url = getenv("LITELLM_URL") + if url: + return url + + host = getenv("LITELLM_HOST", "localhost") + port = getenv("LITELLM_PORT", 8002) + + return f"http://{host}:{port}" + +def get_vector_db_url() -> str: + url = getenv("VECTOR_DB_URL") + if url: + return url + + host = getenv("VECTOR_DB_HOST", "localhost") + port = getenv("VECTOR_DB_PORT", 8010) + + return f"http://{host}:{port}" + +def get_embedding_url(): + url = getenv("EMBEDDING_URL") + if url: + return url + + host = getenv("EMBEDDING_HOST", "localhost") + port = getenv("EMBEDDING_PORT", 8020) + + return f"http://{host}:{port}" + +mongo_database = get_mongo_database_name() + +mongo_url = get_mongo_url() + +litellm_url = get_litellm_url() + +vector_db_url = get_vector_db_url() + +redis_url = get_redis_url() + +embedding_url = get_embedding_url() diff --git a/core/local_model.py b/core/local_model.py index e00b52d..e74bada 100644 --- a/core/local_model.py +++ b/core/local_model.py @@ -17,6 +17,8 @@ ) from openai import OpenAI +import core.config as configs + ner = spacy.load("en_core_web_sm") pattern = r">(.*?) List[List[float]]: diff --git a/core/tools/knowledge/vector_db/milvus/operations.py b/core/tools/knowledge/vector_db/milvus/operations.py index 430f29f..b456d49 100644 --- a/core/tools/knowledge/vector_db/milvus/operations.py +++ b/core/tools/knowledge/vector_db/milvus/operations.py @@ -9,12 +9,16 @@ from .custom_embeddigs import CustomEmbeddings from .query_milvus import Milvus -MILVUS_HOST = os.getenv("MILVUS_HOST", "localhost") - model = {} top_re_rank = 5 top_k_match = 10 +milvus_connection_alias = Milvus.create_connection_alias({ + "host": os.getenv("MILVUS_HOST", "localhost"), + "port": os.getenv("MILVUS_PORT", "19530"), + "user": os.getenv("MILVUS_USER", os.getenv("MILVUS_USERNAME", "")), + "password": os.getenv("MILVUS_PASS", os.getenv("MILVUS_PASSWORD", "")) +}) class Query(BaseModel): text: str @@ -27,17 +31,11 @@ class Query(BaseModel): def drop_collection(collection_name: str): load_collection(collection_name).drop_collection() - def load_collection(collection_name: str) -> Milvus: return Milvus( embedding_function=CustomEmbeddings(), collection_name=collection_name, - connection_args={ - "host": MILVUS_HOST, - "port": "19530", - "user": "username", - "password": "password", - }, + alias=milvus_connection_alias, index_params={ "metric_type": "IP", "index_type": "FLAT", diff --git a/core/tools/knowledge/vector_db/milvus/query_milvus.py b/core/tools/knowledge/vector_db/milvus/query_milvus.py index 4e7779c..35b6677 100644 --- a/core/tools/knowledge/vector_db/milvus/query_milvus.py +++ b/core/tools/knowledge/vector_db/milvus/query_milvus.py @@ -32,6 +32,7 @@ def __init__( embedding_function: Embeddings, collection_name: str = "DefaultCollection", connection_args: Optional[Dict[str, Any]] = None, + alias: Optional[str] = None, consistency_level: str = "Session", index_params: Optional[Dict[str, Any]] = None, search_params: Optional[Dict[str, Any]] = None, @@ -129,9 +130,13 @@ def __init__( self._vector_field = "vector" self.fields: list[str] = [] # Create the connection to the server - if connection_args is None: - connection_args = DEFAULT_MILVUS_CONNECTION - self.alias = self._create_connection_alias(connection_args) + if alias is not None: + self.alias = alias + elif connection_args is not None: + self.alias = Milvus.create_connection_alias(connection_args) + else: + raise ValueError('alias or connection_args must be passed to Milvus construtor') + self.col: Optional[Collection] = None # Grab the existing colection if it exists @@ -154,11 +159,18 @@ def drop_collection(self): utility.drop_collection(collection_name=self.collection_name, using=self.alias) - def _create_connection_alias(self, connection_args: dict) -> str: + @staticmethod + def create_connection_alias(connection_args: dict) -> str: """Create the connection to the Milvus server.""" # Third Party from pymilvus import MilvusException, connections + if connection_args is None: + connection_args = DEFAULT_MILVUS_CONNECTION + else: + # fill anything not passed like "default" port + connection_args = {**connection_args, **DEFAULT_MILVUS_CONNECTION} + # Grab the connection arguments that are used for checking existing connection host: str = connection_args.get("host", None) port: Union[str, int] = connection_args.get("port", None) diff --git a/services/backend/api_server/Dockerfile b/services/backend/api_server/Dockerfile index 4a905e5..eaea120 100644 --- a/services/backend/api_server/Dockerfile +++ b/services/backend/api_server/Dockerfile @@ -3,9 +3,9 @@ FROM python:3.10.7-slim # Set the working directory in the container to /app WORKDIR /app -# Add the current directory contents into the container at /app -COPY . /app -COPY --from=core ./ /app/core +RUN apt-get update && apt-get install gcc g++ -y + +COPY requirements.txt /app # Install any needed packages specified in requirements.txt RUN pip install --no-cache-dir -r requirements.txt @@ -13,6 +13,10 @@ RUN spacy download en_core_web_sm RUN playwright install RUN playwright install-deps +# Add the current directory contents into the container at /app +COPY . /app +COPY --from=core ./ /app/core + # Make port 80 available to the world outside this container EXPOSE 8000 diff --git a/services/backend/api_server/app/backend.py b/services/backend/api_server/app/backend.py index 9d67d55..8adda63 100644 --- a/services/backend/api_server/app/backend.py +++ b/services/backend/api_server/app/backend.py @@ -1,14 +1,17 @@ # Standard Library import asyncio +import os import json import logging -import os import uuid from datetime import datetime -from typing import Any, Dict, Optional +from typing import Any, Dict, Optional, Union, Callable + +from pymongo.server_api import ServerApi -# Third Party import aioredis + +# Third Party import requests from beanie import init_beanie from celery import Celery @@ -87,7 +90,7 @@ delete_docs, drop_collection, ) -from fastapi import FastAPI, Form, HTTPException, UploadFile, WebSocket +from fastapi import FastAPI, Form, HTTPException, UploadFile, WebSocket, Response, status from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import StreamingResponse from fastapi.websockets import WebSocketState @@ -104,9 +107,7 @@ generate_thread_id, ) -litellm_host = os.getenv("LITELLM_HOST", "localhost") -redis_host = os.getenv("REDIS_HOST", "localhost") -mongodb_host = os.getenv("MONGODB_HOST", "localhost") +import core.config as configs app = FastAPI() @@ -125,23 +126,41 @@ ) # MongoDB Configurationget -MONGODB_URL = f"mongodb://{mongodb_host}:27017" -DATABASE_NAME = "rubra_db" -LITELLM_URL = f"http://{litellm_host}:8002" +LITELLM_URL = configs.litellm_url +LITELLM_MASTER_KEY = os.getenv("LITELLM_MASTER_KEY", "") HEADERS = {"accept": "application/json", "Content-Type": "application/json"} # Initialize MongoDB client -mongo_client = AsyncIOMotorClient(MONGODB_URL) -database = mongo_client[DATABASE_NAME] +mongo_client = AsyncIOMotorClient(configs.mongo_url) +database = mongo_client[configs.mongo_database] -celery_app = Celery(broker=f"redis://{redis_host}:6379/0") +celery_app = Celery(broker=configs.redis_url) -logging.basicConfig(level=logging.INFO) +print(configs.redis_url) +print(celery_app.control.ping()) + +redis = aioredis.from_url(configs.redis_url) + +logging.basicConfig(level=logging.INFO) def get_database(): return database +async def full_check() -> None: + await redis.ping() + print("Redis connection is ready!") + + await mongo_client.admin.command("ping") + print("MongoDB connection is ready!") + + res = requests.get(f"{LITELLM_URL}/health/readiness", { }) + + if res.json().get("status", "") != "healthy": + raise Exception("litellm not ready: " + str(res.json())) + + print("litellm is ready!") + @app.on_event("startup") async def on_startup(): @@ -158,7 +177,10 @@ async def on_startup(): ], ) + await full_check() + available_models = [r.id for r in litellm_list_model().data] + print(available_models) if not available_models: logging.warning("No models configured.") return @@ -179,32 +201,29 @@ async def on_startup(): welcome_asst_instruction += tool_use_instruction # Create the Welcome Assistant if it doesn't exist - existing_assistant = await AssistantObject.find_one({"id": "asst_welcome"}) - if not existing_assistant: - logging.info("Creating Welcome Assistant") - assistant = AssistantObject( - assistant_id="asst_welcome", - object=Object20.assistant.value, - created_at=int(datetime.now().timestamp()), - name="Welcome Assistant", - description="Welcome Assistant", - model=welcome_asst_model, - instructions=welcome_asst_instruction, - tools=[{"type": Type824.retrieval.value}] - if welcome_asst_model in tool_enabled_model_pool - else [], # browser - file_ids=[], - metadata={}, - ) - await assistant.insert() + # existing_assistant = await AssistantObject.find_one({"id": "asst_welcome"}) + # if not existing_assistant: + # logging.info("Creating Welcome Assistant") + # assistant = AssistantObject( + # assistant_id="asst_welcome", + # object=Object20.assistant.value, + # created_at=int(datetime.now().timestamp()), + # name="Welcome Assistant", + # description="Welcome Assistant", + # model=welcome_asst_model, + # instructions=welcome_asst_instruction, + # tools=[{"type": Type824.retrieval.value}] + # if welcome_asst_model in tool_enabled_model_pool + # else [], # browser + # file_ids=[], + # metadata={}, + # ) + # await assistant.insert() @app.get("/get_api_key_status", tags=["API Keys"]) async def get_api_key_status(): try: - redis = await aioredis.from_url( - f"redis://{redis_host}:6379/0", encoding="utf-8", decode_responses=True - ) openai_key = await redis.get("OPENAI_API_KEY") anthropic_key = await redis.get("ANTHROPIC_API_KEY") @@ -225,10 +244,6 @@ async def get_api_key_status(): @app.post("/set_api_keys", tags=["API Keys"]) async def set_api_key_status(api_keys: ApiKeysUpdateModel): try: - redis = await aioredis.from_url( - f"redis://{redis_host}:6379/0", encoding="utf-8", decode_responses=True - ) - logging.info("Setting API keys") logging.info(api_keys) @@ -751,9 +766,6 @@ async def list_messages( async def redis_subscriber(channel, timeout=1): logging.info(f"Connecting to Redis and subscribing to channel: {channel}") - redis = await aioredis.from_url( - f"redis://{redis_host}:6379/0", encoding="utf-8", decode_responses=True - ) pubsub = redis.pubsub() await pubsub.subscribe(channel) @@ -778,12 +790,8 @@ async def listen_for_task_status( task_status_channel, status_update_event, thread_id, run_id ): logging.info(f"Listening for task status on channel: {task_status_channel}") - redis = None pubsub = None try: - redis = await aioredis.from_url( - f"redis://{redis_host}:6379/0", encoding="utf-8", decode_responses=True - ) pubsub = redis.pubsub() await pubsub.subscribe(task_status_channel) @@ -1023,12 +1031,12 @@ def convert_model_info_to_oai_model(obj, predefined_models): def litellm_list_model() -> ListModelsResponse: try: - client = OpenAI(base_url=LITELLM_URL, api_key="abc") + client = OpenAI(base_url=LITELLM_URL, api_key=LITELLM_MASTER_KEY) models_data = client.models.list().data models_data = sorted(models_data, key=lambda x: x.id) predefined_models = [convert_to_model(m) for m in models_data] - models_data = requests.get(f"{LITELLM_URL}/model/info").json().get("data", []) + models_data = requests.get(f"{LITELLM_URL}/model/info", headers={"Authorization": f"Bearer {LITELLM_MASTER_KEY}"}).json().get("data", []) models = [ convert_model_info_to_oai_model(m, predefined_models) for m in models_data ] @@ -1543,7 +1551,7 @@ async def get_run_step( tags=["chat/completions"], ) async def chat_completion(body: CreateChatCompletionRequest): - client = OpenAI(base_url=LITELLM_URL, api_key="abc") + client = OpenAI(base_url=LITELLM_URL, api_key=LITELLM_MASTER_KEY) chat_messages = [ {"role": m.__root__.role.value, "content": m.__root__.content} for m in body.messages @@ -1589,6 +1597,10 @@ async def chat_completion(body: CreateChatCompletionRequest): else: return response +@app.get("/healthz/liveness", status_code=status.HTTP_204_NO_CONTENT) +def ping(): + pass + def data_generator(response): """ diff --git a/services/backend/api_server/core b/services/backend/api_server/core new file mode 120000 index 0000000..11716c4 --- /dev/null +++ b/services/backend/api_server/core @@ -0,0 +1 @@ +/Users/debdut/Documents/Repos/rubra-rc/core \ No newline at end of file diff --git a/services/backend/api_server/requirements.txt b/services/backend/api_server/requirements.txt index 9ac8f68..81a29e0 100644 --- a/services/backend/api_server/requirements.txt +++ b/services/backend/api_server/requirements.txt @@ -4,7 +4,7 @@ celery==5.3.6 fastapi==0.105.0 motor==3.3.2 openai==1.6.1 -pymilvus==2.3.4 +pymilvus==2.2.8 pydantic==1.10.9 python-multipart==0.0.6 redis==5.0.1 @@ -16,4 +16,4 @@ langchain==0.0.351 spacy==3.7.2 markdownify==0.11.6 playwright==1.39.0 -tiktoken==0.5.2 \ No newline at end of file +tiktoken==0.5.2 diff --git a/services/backend/task_executor/Dockerfile b/services/backend/task_executor/Dockerfile index 4844f47..8d8fce4 100644 --- a/services/backend/task_executor/Dockerfile +++ b/services/backend/task_executor/Dockerfile @@ -23,4 +23,4 @@ WORKDIR /app ENV OBJC_DISABLE_INITIALIZE_FORK_SAFETY=YES # Run app.py when the container launches -CMD ["celery", "-A", "core.tasks.tasks", "worker", "--loglevel=info"] +CMD ["sh", "-c", "celery -A core.tasks.tasks worker --loglevel=info -n celery@$(hostname)"] diff --git a/services/backend/task_executor/core b/services/backend/task_executor/core new file mode 120000 index 0000000..11716c4 --- /dev/null +++ b/services/backend/task_executor/core @@ -0,0 +1 @@ +/Users/debdut/Documents/Repos/rubra-rc/core \ No newline at end of file diff --git a/services/backend/task_executor/requirements.txt b/services/backend/task_executor/requirements.txt index 00a3ea2..958ecca 100644 --- a/services/backend/task_executor/requirements.txt +++ b/services/backend/task_executor/requirements.txt @@ -12,10 +12,10 @@ redis==5.0.1 requests==2.31.0 uvicorn==0.25.0 websockets==12.0 -pymilvus==2.3.4 +pymilvus==2.2.8 pypdf2==3.0.1 spacy==3.7.2 markdownify==0.11.6 playwright==1.39.0 tiktoken==0.5.2 -chardet==5.2.0 \ No newline at end of file +chardet==5.2.0 diff --git a/services/backend/text_embedding_api/core b/services/backend/text_embedding_api/core new file mode 120000 index 0000000..11716c4 --- /dev/null +++ b/services/backend/text_embedding_api/core @@ -0,0 +1 @@ +/Users/debdut/Documents/Repos/rubra-rc/core \ No newline at end of file diff --git a/services/backend/vector_db_api/core b/services/backend/vector_db_api/core new file mode 120000 index 0000000..11716c4 --- /dev/null +++ b/services/backend/vector_db_api/core @@ -0,0 +1 @@ +/Users/debdut/Documents/Repos/rubra-rc/core \ No newline at end of file diff --git a/services/backend/vector_db_api/main.py b/services/backend/vector_db_api/main.py index 152d832..320605b 100644 --- a/services/backend/vector_db_api/main.py +++ b/services/backend/vector_db_api/main.py @@ -11,7 +11,7 @@ get_similar_match, load_collection, ) -from fastapi import FastAPI +from fastapi import FastAPI, status, Response model = {} top_re_rank = 5 @@ -19,11 +19,6 @@ app = FastAPI() -@app.on_event("startup") -async def app_startup(): - pass - - @app.post("/add_texts") async def add_texts_embeddings( collection_name: str, diff --git a/services/frontend/ui/app/data.txt b/services/frontend/ui/app/data.txt new file mode 100644 index 0000000..5407594 --- /dev/null +++ b/services/frontend/ui/app/data.txt @@ -0,0 +1 @@ +world sucks always is the biggest truth diff --git a/services/frontend/ui/app/rubra_ui_config.py b/services/frontend/ui/app/rubra_ui_config.py index 2e7e167..b2f49cf 100644 --- a/services/frontend/ui/app/rubra_ui_config.py +++ b/services/frontend/ui/app/rubra_ui_config.py @@ -9,7 +9,7 @@ openai.api_key = "..." rubra_backend_host = os.getenv("RUBRA_BACKEND_HOST", "localhost") -RUBRA_BACKEND_URL = f"http://{rubra_backend_host}:8000" +RUBRA_BACKEND_URL = f"http://{rubra_backend_host}:9000" rubra_client = OpenAI(base_url=RUBRA_BACKEND_URL, api_key="abc") def get_all_assistants():