diff --git a/.env.example b/.env.example index 4cad567..2fec06f 100644 --- a/.env.example +++ b/.env.example @@ -1,21 +1,13 @@ -# General -CONFIG_FILE=./config.yml +CONFIG_FILE=./config.example.yml -# Dependencies -OPENGATELLM_URL=https://albert.api.etalab.gouv.fr +OPENGATELLM_URL=http://localhost:8000 OPENGATELLM_MODEL_NAME=openweight-embedding OPENGATELLM_MODEL_VECTOR_SIZE=1024 -POSTGRES_HOST=localhost -POSTGRES_PORT=5432 -POSTGRES_USER=postgres -POSTGRES_PASSWORD=changeme +## Must be the database of the API specified in OPENGATELLM_URL variable ! +OPENGATELLM_DATABASE_URL=postgresql+asyncpg://postgres:changeme@localhost:5432/postgres ELASTICSEARCH_HOST=localhost ELASTICSEARCH_PORT=9200 ELASTICSEARCH_USERNAME=elastic ELASTICSEARCH_PASSWORD=changeme - -# For e2e tests -OPENGATELLM_USER_API_KEY= -OPENGATELLM_ADMIN_API_KEY= diff --git a/.github/tests/compose.yml b/.github/tests/compose.yml index 415f324..dabe661 100644 --- a/.github/tests/compose.yml +++ b/.github/tests/compose.yml @@ -1,21 +1,33 @@ services: - postgres: - extends: - file: ../../compose.example.yml - service: postgres - elasticsearch: extends: file: ../../compose.example.yml service: elasticsearch + postgres: + image: postgres:16.5 + restart: always + user: postgres + environment: + - "POSTGRES_USER=postgres" + - "POSTGRES_PASSWORD=changeme" + - "POSTGRES_DB=postgres" + ports: + - "5432:5432" + volumes: + - postgres:/var/lib/postgresql/data + healthcheck: + test: [ "CMD-SHELL", "pg_isready", "-U", "postgres" ] + interval: 4s + timeout: 10s + retries: 5 + start_period: 60s + redis: image: redis/redis-stack-server:7.4.0-v7 restart: always environment: REDIS_ARGS: "--dir /data --requirepass changeme --user redis on >password ~* allcommands --save 60 1 --appendonly yes" - ports: - - "6379:6379" volumes: - redis:/data healthcheck: diff --git a/.github/tests/start_services.sh b/.github/tests/start_services.sh index 6e02f52..ecca565 100755 --- a/.github/tests/start_services.sh +++ b/.github/tests/start_services.sh @@ -8,11 +8,25 @@ dump_api_diagnostics() { docker compose --file "${COMPOSE_FILE}" ps || true echo "::endgroup::" - echo "::group::API container logs" - docker compose --file "${COMPOSE_FILE}" logs --no-color api || true + echo "::group::Docker Compose logs (all services)" + docker compose --file "${COMPOSE_FILE}" logs --no-color || true echo "::endgroup::" + + echo "::group::API health probe" + curl -sv "http://localhost:8000/health" || true + echo "::endgroup::" +} + +on_error() { + exit_code=$? + line_no=${1:-unknown} + echo "start_services.sh failed (exit=${exit_code}) at line ${line_no}." >&2 + dump_api_diagnostics + exit "${exit_code}" } +trap 'on_error $LINENO' ERR + if ! docker compose --file "${COMPOSE_FILE}" up --detach --wait; then echo "Services did not become healthy." >&2 dump_api_diagnostics diff --git a/.github/workflows/build_and_deploy.yml b/.github/workflows/build_and_deploy.yml index e754151..ff77f4a 100644 --- a/.github/workflows/build_and_deploy.yml +++ b/.github/workflows/build_and_deploy.yml @@ -15,7 +15,7 @@ jobs: name: Build and push OpenGateRAG image runs-on: ubuntu-latest env: - API_IMAGE_NAME: ghcr.io/etalab-ia/opengaterag + API_IMAGE_NAME: ghcr.io/opengaterag IMAGE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || 'latest' }} outputs: commit_title: ${{ steps.get_head_commit_title.outputs.title }} @@ -52,7 +52,7 @@ jobs: needs: build-opengaterag uses: ./.github/workflows/trivy-scan.yml with: - image-name: ghcr.io/etalab-ia/opengaterag + image-name: ghcr.io/opengaterag image-tag: ${{ github.event_name == 'release' && github.event.release.tag_name || 'latest' }} # deploy-dev: diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index 1e53657..9053bc6 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -65,6 +65,17 @@ jobs: ALBERT_ADMIN_API_KEY: ${{ secrets.ALBERT_ADMIN_API_KEY }} OPENGATELLM_MODEL_NAME: openweight-embeddings + - name: Dump Docker Compose logs on failure + if: failure() + run: | + echo "::group::Docker Compose status" + docker compose --file .github/tests/compose.yml ps || true + echo "::endgroup::" + + echo "::group::Docker Compose logs (all services)" + docker compose --file .github/tests/compose.yml logs --no-color || true + echo "::endgroup::" + - name: Run e2e tests run: | # Create a local virtual environment and install dependencies @@ -79,3 +90,4 @@ jobs: OPENGATELLM_URL: http://localhost:8000 OPENGATELLM_MODEL_NAME: openweight-embeddings OPENGATELLM_MODEL_VECTOR_SIZE: 1024 + OPENGATELLM_DATABASE_URL: postgresql+asyncpg://postgres:changeme@localhost:5432/postgres diff --git a/README.md b/README.md index d056289..8d77d3e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,9 @@ export $(cat .env | grep -v ^# | xargs) ``` + > [!NOTE] + > The database setup by `OPENGATELLM_DATABASE_URL` environment variable must be the same as the database used by the API specified in `OPENGATELLM_URL` environment variable. + * Run API locally ```bash diff --git a/compose.example.yml b/compose.example.yml index 9a9475a..83bee7e 100644 --- a/compose.example.yml +++ b/compose.example.yml @@ -2,7 +2,7 @@ name: opengatellm services: rag: - image: ghcr.io/etalab-ia/opengatellm/rag:latest + image: ghcr.io/opengaterag:latest restart: always env_file: .env environment: @@ -14,41 +14,6 @@ services: healthcheck: test: [ "CMD-SHELL", "curl -sf http://localhost:8000/health || exit 1" ] - postgres: - image: postgres:16.5 - restart: always - user: postgres - environment: - - "POSTGRES_USER=${POSTGRES_USER:-postgres}" - - "POSTGRES_PASSWORD=${POSTGRES_PASSWORD:-changeme}" - - "POSTGRES_DB=postgres" - ports: - - "${POSTGRES_PORT:-5432}:5432" - volumes: - - postgres:/var/lib/postgresql/data - healthcheck: - test: [ "CMD-SHELL", "pg_isready", "-U", "postgres" ] - interval: 4s - timeout: 10s - retries: 5 - start_period: 60s - - redis: - image: redis/redis-stack-server:7.4.0-v7 - restart: always - environment: - REDIS_ARGS: "--dir /data --requirepass ${REDIS_PASSWORD:-changeme} --user ${REDIS_USER:-redis} on >password ~* allcommands --save 60 1 --appendonly yes" - ports: - - "${REDIS_PORT:-6379}:6379" - volumes: - - redis:/data - healthcheck: - test: [ "CMD", "redis-cli", "--raw", "incr", "ping" ] - interval: 4s - timeout: 10s - retries: 5 - start_period: 60s - elasticsearch: image: docker.elastic.co/elasticsearch/elasticsearch:9.0.2 restart: always diff --git a/config.example.yml b/config.example.yml index 4362dea..7dce461 100644 --- a/config.example.yml +++ b/config.example.yml @@ -1,12 +1,12 @@ # -------------------------------- dependencies --------------------------------- dependencies: opengatellm: - url: ${OPENGATELLM_URL:-https://albert.api.etalab.gouv.fr} + url: ${OPENGATELLM_URL:-http://localhost:8000} model_name: ${OPENGATELLM_MODEL_NAME:-openweight-embedding} model_vector_size: ${OPENGATELLM_MODEL_VECTOR_SIZE:-1024} - postgres: # required - url: postgresql+asyncpg://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-changeme}@${POSTGRES_HOST:-localhost}:${POSTGRES_PORT:-5432}/${POSTGRES_DB:-postgres} + postgres: + url: ${OPENGATELLM_DATABASE_URL:-postgresql+asyncpg://postgres:changeme@localhost:5432/postgres} echo: False pool_size: 5 connect_args: @@ -14,10 +14,9 @@ dependencies: statement_timeout: "120s" command_timeout: 60 - elasticsearch: - index_name: opengatellm - index_language: english + index_name: opengaterag + index_language: french number_of_shards: 1 number_of_replicas: 0 refresh_interval: 1s @@ -30,13 +29,13 @@ dependencies: settings: # disabled_routers: ["collections"] # hidden_routers: ["documents"] - # app_title: My OpenGateLLM API + # app_title: My OpenGateRAG API # log_level: INFO # log_format: [%(asctime)s][%(process)d:%(name)s][%(levelname)s] %(client_ip)s - %(message)s swagger_version: 0.1.0 - # swagger_contact_url: https://github.com/etalab-ia/OpenGateLLM + # swagger_contact_url: https://github.com/etalab-ia/OpenGateRAG # swagger_contact_email: john.doe@example.com # swagger_docs_url: /docs # swagger_redoc_url: /redoc diff --git a/opengaterag/api/utils/configuration.py b/opengaterag/api/utils/configuration.py index cf46208..8d4a985 100644 --- a/opengaterag/api/utils/configuration.py +++ b/opengaterag/api/utils/configuration.py @@ -99,12 +99,12 @@ class ConfigBaseModel(BaseModel): @custom_validation_error() class ElasticsearchDependency(ConfigBaseModel): """ - Elasticsearch is an optional dependency of OpenGateLLM. Elasticsearch is used as a vector store. If this dependency is provided, all documents endpoint are enabled. + Elasticsearch is a required dependency of OpenGateRAG. Elasticsearch is used as a vector store. If this dependency is provided, all documents endpoint are enabled. Pass all arguments of `elasticsearch.Elasticsearch` class, see https://elasticsearch-py.readthedocs.io/en/latest/api/elasticsearch.html for more information. Other arguments declared below are used to configure the Elasticsearch index. """ - index_name: constr(strip_whitespace=True, min_length=1) = Field(default="opengatellm", description="Name of the Elasticsearch index.", examples=["my_index"]) # fmt: off + index_name: constr(strip_whitespace=True, min_length=1) = Field(default="opengaterag", description="Name of the Elasticsearch index.", examples=["my_index"]) # fmt: off index_language: ElasticsearchIndexLanguage = Field(default=ElasticsearchIndexLanguage.ENGLISH, description="Language of the Elasticsearch index.", examples=[ElasticsearchIndexLanguage.ENGLISH.value]) # fmt: off number_of_shards: int = Field(default=12, ge=1, le=75, description="Number of shards for the Elasticsearch index.", examples=[4]) # fmt: off number_of_replicas: int = Field(default=1, ge=0, description="Number of replicas for the Elasticsearch index.", examples=[1]) # fmt: off @@ -125,7 +125,7 @@ class OpengateLLMDependency(ConfigBaseModel): @custom_validation_error() class PostgresDependency(ConfigBaseModel): """ - Postgres is a required dependency of OpenGateLLM. In this section, you can pass all postgres python SDK arguments, see https://docs.sqlalchemy.org/en/21/core/engines.html#engine-creation-apihttps://docs.sqlalchemy.org/en/21/core/engines.html#engine-creation-api for more information. + Postgres is a required dependency of OpenGateRAG. In this section, you can pass all postgres python SDK arguments, see https://docs.sqlalchemy.org/en/21/core/engines.html#engine-creation-apihttps://docs.sqlalchemy.org/en/21/core/engines.html#engine-creation-api for more information. Only the `url` argument is required. The connection URL must use the asynchronous scheme, `postgresql+asyncpg://`. If you provide a standard `postgresql://` URL, it will be automatically converted to use asyncpg. """ @@ -194,11 +194,11 @@ class Settings(ConfigBaseModel): usage_tokenizer: Tokenizer = Field(default=Tokenizer.TIKTOKEN_GPT2, description="Tokenizer used to compute usage of the API.") # swagger - swagger_summary: str = Field(default="OpenGateLLM connect to your models. You can configuration this swagger UI in the configuration file, like hide routes or change the title.", description="Display summary of your API in swagger UI, see https://fastapi.tiangolo.com/tutorial/metadata for more information.", examples=["My API description."]) # fmt: off + swagger_summary: str = Field(default="You can configuration this swagger UI in the configuration file, like hide routes or change the title.", description="Display summary of your API in swagger UI, see https://fastapi.tiangolo.com/tutorial/metadata for more information.", examples=["My API description."]) # fmt: off swagger_version: str = Field(default="latest", description="Display version of your API in swagger UI, see https://fastapi.tiangolo.com/tutorial/metadata for more information.", examples=["2.5.0"]) # fmt: off - swagger_description: str = Field(default="[See documentation](https://github.com/etalab-ia/opengatellm/blob/main/README.md)", description="Display description of your API in swagger UI, see https://fastapi.tiangolo.com/tutorial/metadata for more information.", examples=["[See documentation](https://github.com/etalab-ia/opengatellm/blob/main/README.md)"]) # fmt: off + swagger_description: str = Field(default="[See documentation](https://github.com/etalab-ia/opengaterag/blob/main/README.md)", description="Display description of your API in swagger UI, see https://fastapi.tiangolo.com/tutorial/metadata for more information.", examples=["[See documentation](https://github.com/etalab-ia/opengaterag/blob/main/README.md)"]) # fmt: off swagger_contact: dict | None = Field(default=None, description="Contact informations of the API in swagger UI, see https://fastapi.tiangolo.com/tutorial/metadata for more information.") # fmt: off - swagger_license_info: dict = Field(default={"name": "MIT Licence", "identifier": "MIT", "url": "https://raw.githubusercontent.com/etalab-ia/opengatellm/refs/heads/main/LICENSE"}, description="Licence informations of the API in swagger UI, see https://fastapi.tiangolo.com/tutorial/metadata for more information.") # fmt: off + swagger_license_info: dict = Field(default={"name": "MIT Licence", "identifier": "MIT", "url": "https://raw.githubusercontent.com/etalab-ia/opengaterag/refs/heads/main/LICENSE"}, description="Licence informations of the API in swagger UI, see https://fastapi.tiangolo.com/tutorial/metadata for more information.") # fmt: off swagger_terms_of_service: str | None = Field(default=None, description="A URL to the Terms of Service for the API in swagger UI. If provided, this has to be a URL.", examples=["https://example.com/terms-of-service"]) # fmt: off swagger_openapi_tags: list[dict[str, str | dict[str, str]]] = Field(default_factory=list, description="OpenAPI tags of the API in swagger UI, see https://fastapi.tiangolo.com/tutorial/metadata for more information.", json_schema_extra={"default": []}) # fmt: off swagger_openapi_url: str = Field(default="/openapi.json", pattern=r"^/", description="OpenAPI URL of swagger UI, see https://fastapi.tiangolo.com/tutorial/metadata for more information.") # fmt: off