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
90 changes: 90 additions & 0 deletions .github/workflows/api_tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: API Tests

on:
push:
branches: [main, dev]
pull_request:

jobs:
api-tests:
runs-on: ubuntu-latest
timeout-minutes: 15

steps:
- name: Checkout repository
uses: actions/checkout@v4
with:
submodules: true

- name: Set up Docker Compose
uses: docker/setup-compose-action@v1
with:
version: v2.34.0

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Install test dependencies
run: |
pip install pytest httpx pytest-timeout

- name: Build and start services
working-directory: .github/workflows/api_tests
run: |
docker compose build
docker compose up -d

echo "Waiting for services to be ready..."

# Wait for mock-vllm
echo "Waiting for mock-vllm..."
if ! timeout 60 bash -c 'until curl -sf http://localhost:8000/health 2>/dev/null; do sleep 2; done'; then
echo "Mock VLLM failed to start"
docker compose logs mock-vllm
exit 1
fi

# Wait for OpenRAG API
echo "Waiting for OpenRAG API..."
for i in {1..60}; do
if curl -sf http://localhost:8080/health_check 2>/dev/null; then
echo "OpenRAG API ready after $i attempts"
break
fi
echo "Attempt $i/60 - waiting..."
sleep 5
done

# Final check
if ! curl -sf http://localhost:8080/health_check; then
echo "OpenRAG API failed to start"
docker compose logs openrag
exit 1
fi

echo "All services ready!"

- name: Run API tests
env:
OPENRAG_API_URL: http://localhost:8080
run: |
pytest tests/api_tests/ -v --timeout=120 --tb=short

- name: Show logs on failure
if: failure()
working-directory: .github/workflows/api_tests
run: |
echo "=== OpenRAG Logs ==="
docker compose logs openrag --tail=100
echo "=== Mock VLLM Logs ==="
docker compose logs mock-vllm --tail=50
echo "=== Milvus Logs ==="
docker compose logs milvus --tail=50

- name: Cleanup
if: always()
working-directory: .github/workflows/api_tests
run: |
docker compose down -v
6 changes: 6 additions & 0 deletions .github/workflows/api_tests/Dockerfile.mock-vllm
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
FROM python:3.12-slim
WORKDIR /app
RUN apt-get update && apt-get install -y --no-install-recommends curl && rm -rf /var/lib/apt/lists/*
RUN pip install --no-cache-dir fastapi uvicorn pydantic
COPY mock_vllm.py .
CMD ["python", "mock_vllm.py"]
129 changes: 129 additions & 0 deletions .github/workflows/api_tests/docker-compose.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
# Minimal compose for API testing without VLLM
# Uses mock embedding server instead

services:
# Mock VLLM embedding server
mock-vllm:
build:
context: .
dockerfile: Dockerfile.mock-vllm
ports:
- "8000:8000"
networks:
default:
aliases:
- vllm
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
interval: 2s
timeout: 5s
retries: 10
start_period: 5s

# PostgreSQL for user/partition management
rdb:
image: postgres:15
environment:
- POSTGRES_PASSWORD=root_password
- POSTGRES_USER=root
healthcheck:
test: ["CMD-SHELL", "pg_isready -U root"]
interval: 2s
timeout: 5s
retries: 10

# Milvus dependencies
etcd:
image: quay.io/coreos/etcd:v3.5.16
environment:
- ETCD_AUTO_COMPACTION_MODE=revision
- ETCD_AUTO_COMPACTION_RETENTION=1000
- ETCD_QUOTA_BACKEND_BYTES=4294967296
command: etcd -advertise-client-urls=http://127.0.0.1:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
healthcheck:
test: ["CMD", "etcdctl", "endpoint", "health"]
interval: 5s
timeout: 10s
retries: 5

minio:
image: minio/minio:RELEASE.2023-03-20T20-16-18Z
environment:
MINIO_ACCESS_KEY: minioadmin
MINIO_SECRET_KEY: minioadmin
command: minio server /minio_data --console-address ":9001"
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9000/minio/health/live"]
interval: 5s
timeout: 10s
retries: 5

milvus:
image: milvusdb/milvus:v2.5.4
command: ["milvus", "run", "standalone"]
security_opt:
- seccomp:unconfined
environment:
ETCD_ENDPOINTS: etcd:2379
MINIO_ADDRESS: minio:9000
depends_on:
etcd:
condition: service_healthy
minio:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
interval: 5s
timeout: 10s
retries: 10
start_period: 30s

# OpenRAG API (build from repo root)
openrag:
build:
context: ../../../
dockerfile: Dockerfile
environment:
- EMBEDDER_MODEL_NAME=mock-embedding-model
- EMBEDDER_BASE_URL=http://vllm:8000/v1
- EMBEDDER_API_KEY=EMPTY
- POSTGRES_HOST=rdb
- POSTGRES_USER=root
- POSTGRES_PASSWORD=root_password
- VDB_HOST=milvus
- BASE_URL=http://localhost:8001/
- API_KEY=sk-test
- MODEL=test-model
- VLM_BASE_URL=http://localhost:8002/
- VLM_API_KEY=sk-test
- VLM_MODEL=test-vlm
- RERANKER_ENABLED=false
- WITH_CHAINLIT_UI=false
- WITH_OPENAI_API=true
- IMAGE_CAPTIONING=false
- LOG_LEVEL=INFO
- PDFLoader=PyMuPDFLoader
- RAY_POOL_SIZE=1
- RAY_ENABLE_UV_RUN_RUNTIME_ENV=0
- RAY_memory_monitor_refresh_ms=0
- PROMPTS_DIR=../prompts/example1
ports:
- "8080:8080"
volumes:
- test_data:/app/data
depends_on:
mock-vllm:
condition: service_healthy
milvus:
condition: service_healthy
rdb:
condition: service_healthy
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:8080/health_check"]
interval: 5s
timeout: 10s
retries: 20
start_period: 60s

volumes:
test_data:
42 changes: 42 additions & 0 deletions .github/workflows/api_tests/fixtures/sample.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
Sample Document for API Testing

This is a sample document used for testing the OpenRAG API.

Introduction
============

OpenRAG is a Retrieval-Augmented Generation system that combines document indexing
with semantic search capabilities. This document is used to verify that the API
endpoints work correctly.

Content
=======

The system supports various file formats including:
- Plain text files (.txt)
- Markdown documents (.md)
- PDF documents (.pdf)
- Microsoft Word documents (.docx)

Features
========

1. Document Indexing: Upload and index documents for semantic search
2. Semantic Search: Find relevant documents using natural language queries
3. RAG Integration: Combine search results with language models
4. Partition Management: Organize documents into separate collections

Testing
=======

This fixture file is used in automated tests to verify:
- File upload functionality
- Document indexing pipeline
- Search capabilities
- API response formats

Conclusion
==========

The OpenRAG API provides a comprehensive set of endpoints for document management
and retrieval-augmented generation workflows.
80 changes: 80 additions & 0 deletions .github/workflows/api_tests/mock_vllm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""
Lightweight mock VLLM embedding server for CI testing.
Returns deterministic fake embeddings without loading actual models.
"""
import hashlib
from typing import List, Union

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()

# Matches ibm-granite/granite-embedding-small-english-r2 dimension
EMBEDDING_DIM = 384


class EmbeddingRequest(BaseModel):
model: str
input: Union[str, List[str]]
encoding_format: str = "float"


class EmbeddingData(BaseModel):
object: str = "embedding"
embedding: List[float]
index: int


class EmbeddingResponse(BaseModel):
object: str = "list"
data: List[EmbeddingData]
model: str
usage: dict


def generate_fake_embedding(text: str, dim: int = EMBEDDING_DIM) -> List[float]:
"""Generate deterministic fake embedding based on text hash."""
h = hashlib.md5(text.encode()).digest()
result = []
for i in range(dim):
byte_val = h[i % len(h)]
result.append((byte_val / 128.0) - 1.0)
return result


@app.get("/health")
async def health():
return {"status": "ok"}


@app.get("/v1/models")
async def list_models():
return {
"object": "list",
"data": [{"id": "mock-embedding-model", "object": "model"}]
}


@app.post("/v1/embeddings")
async def create_embeddings(request: EmbeddingRequest) -> EmbeddingResponse:
inputs = request.input if isinstance(request.input, list) else [request.input]

data = [
EmbeddingData(
embedding=generate_fake_embedding(text),
index=i
)
for i, text in enumerate(inputs)
]

return EmbeddingResponse(
data=data,
model=request.model,
usage={"prompt_tokens": len(inputs) * 10, "total_tokens": len(inputs) * 10}
)


if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
13 changes: 10 additions & 3 deletions openrag/components/indexer/vectordb/vectordb.py
Original file line number Diff line number Diff line change
Expand Up @@ -709,15 +709,22 @@ async def get_chunk_by_id(self, chunk_id: str):
"""
Retrieve a chunk by its ID.
Args:
chunk_id (str): The ID of the chunk to retrieve.
chunk_id (str): The ID of the chunk to retrieve (Milvus Int64 _id as string).
Returns:
Document: The retrieved chunk.
Document: The retrieved chunk, or None if not found or invalid ID format.
"""
log = self.logger.bind(chunk_id=chunk_id)
# Milvus _id is Int64, so we need to convert the string to int
try:
chunk_id_int = int(chunk_id)
except (ValueError, TypeError):
log.warning("Invalid chunk_id format - must be an integer")
return None

try:
response = await self._async_client.query(
collection_name=self.collection_name,
filter=f"_id == {chunk_id}",
filter=f"_id == {chunk_id_int}",
limit=1,
)
if response:
Expand Down
2 changes: 2 additions & 0 deletions openrag/routers/extract.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,8 @@ async def get_extract(
detail=f"User does not have access to extract '{extract_id}'.",
)
log.info("Extract successfully retrieved.")
except HTTPException:
raise
except Exception as e:
log.exception("Failed to retrieve extract.", error=str(e))
raise HTTPException(
Expand Down
1 change: 1 addition & 0 deletions tests/api_tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
# API Tests Package
Loading