Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/badges/coverage.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"schemaVersion":1,"label":"coverage","message":"71.21%","color":"red"}
{"schemaVersion":1,"label":"coverage","message":"71.11%","color":"red"}
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -219,4 +219,6 @@ __marimo__/

# OpenGateRAG
config.yml
.DS_Store
.DS_Store
compose.yml
uv.lock
2 changes: 1 addition & 1 deletion compose.example.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: opengatellm
name: opengaterag

services:
rag:
Expand Down
3 changes: 1 addition & 2 deletions config.example.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
dependencies:
opengatellm:
url: ${OPENGATELLM_URL:-http://localhost:8000}
model_name: ${OPENGATELLM_MODEL_NAME:-openweight-embedding}
model_name: ${OPENGATELLM_MODEL_NAME:-openweight-embeddings}
model_vector_size: ${OPENGATELLM_MODEL_VECTOR_SIZE:-1024}
# openapi_url: /openapi.json

Expand Down Expand Up @@ -42,4 +42,3 @@ settings:
# swagger_redoc_url: /redoc

# monitoring_sentry_enabled: True
# monitoring_prometheus_enabled: True
14 changes: 6 additions & 8 deletions opengaterag/api/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ RUN apt-get update && apt-get install -y \
python3-dev \
&& rm -rf /var/lib/apt/lists/*

WORKDIR /
WORKDIR /app

COPY ./opengaterag/api/ opengaterag/api/api
COPY ./pyproject.toml /app/pyproject.toml
COPY ./opengaterag /app/opengaterag

RUN --mount=type=cache,target=/root/.cache/uv \
uv venv
uv venv /.venv
RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
uv pip install .
uv pip install --python /.venv/bin/python .

# Runtime stage
FROM python:3.12-slim AS runtime
Expand All @@ -38,15 +38,13 @@ RUN apt-get update && apt-get install -y \

# Set a non-root user
USER opengaterag
WORKDIR /
WORKDIR /app

# Copy application from builder
COPY --from=builder --chown=opengaterag:opengaterag opengaterag/api /api
COPY --from=builder --chown=opengaterag:opengaterag /.venv /.venv
COPY --chown=opengaterag:opengaterag ./scripts/ /scripts/

ENV PATH="/.venv/bin:${PATH}"
ENV PYTHONPATH="/api"
ENV PROMETHEUS_MULTIPROC_DIR="/tmp/prometheus_multiproc"
ENV CONFIG_FILE="/config.yml"

Expand Down
39 changes: 19 additions & 20 deletions opengaterag/api/utils/configuration.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import os
from pathlib import Path
import re
from typing import Any, Literal, get_args, get_origin
from typing import Annotated, Any, Literal, get_args, get_origin

from pydantic import BaseModel, ConfigDict, Field, constr, field_validator, model_validator
from pydantic import BaseModel, ConfigDict, Field, StringConstraints, field_validator, model_validator
from pydantic import ValidationError as PydanticValidationError
from pydantic_settings import BaseSettings
import yaml
Expand Down Expand Up @@ -104,11 +104,11 @@ class ElasticsearchDependency(ConfigBaseModel):
Other arguments declared below are used to configure the Elasticsearch index.
"""

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
refresh_interval: constr(strip_whitespace=True, pattern=r"^(-1|\d+(ms|s|m|h|d))$") = Field(default="1s", description="Refresh interval for the Elasticsearch index", examples=["2s"]) # fmt: off
index_name: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] = Field(default="opengaterag", description="Name of the Elasticsearch index.", examples=["my_index"]) # fmt: off
index_language: Annotated[ElasticsearchIndexLanguage, Field(default=ElasticsearchIndexLanguage.ENGLISH, description="Language of the Elasticsearch index.", examples=[ElasticsearchIndexLanguage.ENGLISH.value])] # fmt: off
number_of_shards: Annotated[int, Field(default=12, ge=1, le=75, description="Number of shards for the Elasticsearch index.", examples=[4])]
number_of_replicas: Annotated[int, Field(default=1, ge=0, description="Number of replicas for the Elasticsearch index.", examples=[1])]
refresh_interval: Annotated[str, StringConstraints(strip_whitespace=True, pattern=r"^(-1|\d+(ms|s|m|h|d))$")] = Field(default="1s", description="Refresh interval for the Elasticsearch index", examples=["2s"]) # fmt: off


@custom_validation_error()
Expand All @@ -117,10 +117,17 @@ class OpengateLLMDependency(ConfigBaseModel):
OpengateLLM is a required dependency of OpenGateLLM. It is used to connect to the OpengateLLM API.
"""

url: constr(strip_whitespace=True, min_length=1) = Field(..., description="URL of the OpengateLLM API.", examples=["https://opengatellm.com"]) # fmt: off
model_name: str = Field(..., description="Model of the vector store to be used to embed the text in the vector store.", examples=["text-embedding-3-small"]) # fmt: off
model_vector_size: int = Field(ge=1, description="Size of the vector to be used to embed the text in the vector store.", examples=[1536]) # fmt: off
openapi_url: str = Field(default="/openapi.json", description="Path, relative to `url`, where OpengateLLM exposes its OpenAPI schema. Used to build the unified API documentation.", examples=["/openapi.json"]) # fmt: off
url: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] = Field(..., description="URL of the OpengateLLM API.", examples=["https://opengatellm.com"]) # fmt: off
model_name: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] = Field(..., description="Model of the vector store to be used to embed the text in the vector store.", examples=["text-embedding-3-small"]) # fmt: off
model_vector_size: Annotated[int, Field(ge=1, description="Size of the vector to be used to embed the text in the vector store.", examples=[1536])] # fmt: off
public_url: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1)] | None = Field(default=None, description="Public URL of the OpengateLLM API to run swagger UI without CORS issues. If not provided, the public URL will be the same as the `url`.") # fmt: off
openapi_url: Annotated[str, Field(default="/openapi.json", description="Path, relative to `url`, where OpengateLLM exposes its OpenAPI schema. Used to build the unified API documentation.", examples=["/openapi.json"])] # fmt: off

@field_validator("public_url", mode="after")
def set_public_url(cls, public_url: str | None) -> str:
if public_url is None:
return cls.url
return public_url


@custom_validation_error()
Expand All @@ -130,15 +137,7 @@ class PostgresDependency(ConfigBaseModel):
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.
"""

url: constr(strip_whitespace=True, min_length=1) = Field(..., pattern=r"^postgresql", description="PostgreSQL connection url.", examples=["postgresql+asyncpg://postgres:changeme@localhost:5432/postgres"]) # fmt: off

@field_validator("url", mode="after")
def force_async(cls, url):
if url.startswith("postgresql://"):
logging.warning(msg="PostgreSQL connection must be async, force asyncpg connection.")
url = url.replace("postgresql://", "postgresql+asyncpg://")

return url
url: Annotated[str, StringConstraints(strip_whitespace=True, min_length=1, pattern=r"^postgresql\+asyncpg://")] = Field(..., description="PostgreSQL connection url.", examples=["postgresql+asyncpg://postgres:changeme@localhost:5432/postgres"]) # fmt: off


@custom_validation_error()
Expand Down
7 changes: 2 additions & 5 deletions opengaterag/api/utils/sql.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,12 @@
from sqlalchemy.orm import Mapped, declarative_base, mapped_column, relationship
from sqlalchemy.types import JSON

from opengaterag.api.schemas.collections import CollectionVisibility

DEFAULT_TIMEOUT = 300
COUNTRY_CODES = [country.alpha_3 for country in pycountry.countries] + ["WOR"]


class CollectionVisibility(StrEnum):
PRIVATE = "private"
PUBLIC = "public"


class LimitType(StrEnum):
TPM = "tpm"
TPD = "tpd"
Expand Down
7 changes: 4 additions & 3 deletions opengaterag/docs/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,10 @@ def create_docs_app(parent_app: FastAPI, configuration: Configuration) -> FastAP
OpenGateRAG schema is served so the documentation never fully breaks.
"""
settings = configuration.settings
opengatellm_url = configuration.dependencies.opengatellm.url.rstrip("/")
opengatellm_private_url = configuration.dependencies.opengatellm.url.rstrip("/")
opengatellm_public_url = (configuration.dependencies.opengatellm.public_url or opengatellm_private_url).rstrip("/")
opengatellm_openapi_path = configuration.dependencies.opengatellm.openapi_url.lstrip("/")
opengatellm_openapi_url = f"{opengatellm_url}/{opengatellm_openapi_path}"
opengatellm_openapi_url = f"{opengatellm_private_url}/{opengatellm_openapi_path}"

docs_app = FastAPI(docs_url=None, redoc_url=None, openapi_url=None)
cache: dict[str, object] = {"schema": None, "expires_at": 0.0}
Expand All @@ -41,7 +42,7 @@ async def build_merged_schema() -> dict:
merged = merge_openapi_schemas(
base=base_schema,
secondary=secondary_schema,
secondary_server_url=opengatellm_url,
secondary_server_url=opengatellm_public_url,
)
except httpx.HTTPError as error:
logger.warning(f"Unable to fetch OpenGateLLM OpenAPI schema from {opengatellm_openapi_url}: {error}")
Expand Down
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,17 @@ test = [
"pytest-cov>=7.0.0",
]

[build-system]
requires = ["setuptools>=70", "wheel"]
build-backend = "setuptools.build_meta"

[tool.setuptools]
py-modules = []

[tool.setuptools.packages.find]
where = ["."]
include = ["opengaterag*"]

[tool.ruff]
line-length = 150
target-version = "py312"
Expand Down
44 changes: 0 additions & 44 deletions scripts/create_api_key.py

This file was deleted.

13 changes: 12 additions & 1 deletion scripts/startup_api.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,19 @@ set -e

GUNICORN_CMD_ARGS=${GUNICORN_CMD_ARGS:-""} # ex: --log-config app/log.conf

# Allow running locally without env vars.
PROMETHEUS_MULTIPROC_DIR=${PROMETHEUS_MULTIPROC_DIR:-/tmp/prometheus_multiproc}
mkdir -p "$PROMETHEUS_MULTIPROC_DIR"
# Empty prometheus dir if it exists, to prevent ghost metrics being ingested twice in case of docker restart
rm -rf "${PROMETHEUS_MULTIPROC_DIR:?}"/*

exec gunicorn opengaterag.api.main:app --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --config scripts/gunicorn.conf.py $GUNICORN_CMD_ARGS
if [[ -f "scripts/gunicorn.conf.py" ]]; then
GUNICORN_CONFIG="scripts/gunicorn.conf.py"
elif [[ -f "/scripts/gunicorn.conf.py" ]]; then
GUNICORN_CONFIG="/scripts/gunicorn.conf.py"
else
echo "Error: gunicorn config not found (expected scripts/gunicorn.conf.py or /scripts/gunicorn.conf.py)" >&2
exit 1
fi

exec gunicorn opengaterag.api.main:app --worker-class uvicorn.workers.UvicornWorker --bind 0.0.0.0:8000 --config "$GUNICORN_CONFIG" $GUNICORN_CMD_ARGS
Loading
Loading