Skip to content

Andrew's Advice #27

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
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
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,5 @@ product_metadata.json
product_vectors.json
data/
!backend/data
.env
.env
.python-version
260 changes: 236 additions & 24 deletions backend/poetry.lock

Large diffs are not rendered by default.

30 changes: 7 additions & 23 deletions backend/productsearch/api/routes/product.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,9 @@

import numpy as np
from fastapi import APIRouter, Depends
from redis.commands.search.document import Document
from redis.commands.search.query import Query
from redisvl.index import AsyncSearchIndex
from redisvl.query import FilterQuery, VectorQuery
from redisvl.query import CountQuery, FilterQuery, VectorQuery
from redisvl.query.filter import FilterExpression, Tag

from productsearch import config
Expand All @@ -16,24 +15,9 @@
)
from productsearch.db import redis_helpers


router = APIRouter()


def create_count_query(filter_expression: FilterExpression) -> Query:
"""
Create a "count" query where simply want to know how many records
match a particular filter expression

Args:
filter_expression (FilterExpression): The filter expression for the query.

Returns:
Query: The Redis query object.
"""
return Query(str(filter_expression)).no_content().dialect(2)


@router.get(
"/",
response_model=ProductSearchResponse,
Expand Down Expand Up @@ -116,14 +100,14 @@ async def find_products_by_image(
return_fields=config.RETURN_FIELDS,
filter_expression=filter_expression,
)
count_query = create_count_query(filter_expression)
count_query = CountQuery(filter_expression)

# Execute search
count, result_papers = await asyncio.gather(
index.search(count_query), index.query(paper_similarity_query)
index.query(count_query), index.query(paper_similarity_query)
)
# Get Paper records of those results
return ProductVectorSearchResponse(total=count.total, products=result_papers)
return ProductVectorSearchResponse(total=count, products=result_papers)


@router.post(
Expand Down Expand Up @@ -174,11 +158,11 @@ async def find_products_by_text(
return_fields=config.RETURN_FIELDS,
filter_expression=filter_expression,
)
count_query = create_count_query(filter_expression)
count_query = CountQuery(filter_expression)

# Execute search
count, result_papers = await asyncio.gather(
index.search(count_query), index.query(paper_similarity_query)
index.query(count_query), index.query(paper_similarity_query)
)
# Get Paper records of those results
return ProductVectorSearchResponse(total=count.total, products=result_papers)
return ProductVectorSearchResponse(total=count, products=result_papers)
7 changes: 4 additions & 3 deletions backend/productsearch/db/load.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@

import numpy as np
import requests
from productsearch import config
from redisvl.index import AsyncSearchIndex

from productsearch import config


def read_from_s3():
res = requests.get(config.S3_DATA_URL)
Expand Down Expand Up @@ -59,9 +60,9 @@ def preprocess(product: dict) -> dict:

async def load_data():
index = AsyncSearchIndex.from_yaml(
os.path.join("./productsearch/db/schema", "products.yml")
os.path.join("./productsearch/db/schema", "products.yml"),
redis_url=config.REDIS_URL,
)
index.connect(config.REDIS_URL)

# Check if index exists
if await index.exists() and len((await index.search("*")).docs) > 0:
Expand Down
17 changes: 6 additions & 11 deletions backend/productsearch/db/redis_helpers.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import logging
import os
from typing import List

from redis.asyncio import Redis
from redisvl.index import AsyncSearchIndex, SearchIndex
from redisvl.query.filter import FilterExpression, Tag
from redisvl.schema import IndexSchema

from productsearch import config
Expand All @@ -15,13 +12,11 @@
dir_path = os.path.dirname(os.path.realpath(__file__)) + "/schema"
file_path = os.path.join(dir_path, "products.yml")
schema = IndexSchema.from_yaml(file_path)
client = Redis.from_url(config.REDIS_URL)
global_index = None
_global_index = None


def get_test_index():
index = SearchIndex.from_yaml(file_path)
index.connect(redis_url=config.REDIS_URL)
index = SearchIndex.from_yaml(file_path, redis_url=config.REDIS_URL)

if not index.exists():
index.create(overwrite=True)
Expand All @@ -30,7 +25,7 @@ def get_test_index():


async def get_async_index():
global global_index
if not global_index:
global_index = AsyncSearchIndex(schema, client)
yield global_index
global _global_index
if not _global_index:
_global_index = AsyncSearchIndex(schema, redis_url=config.REDIS_URL)
return _global_index
10 changes: 10 additions & 0 deletions backend/productsearch/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
from contextlib import asynccontextmanager
from pathlib import Path

import uvicorn
Expand All @@ -8,6 +9,15 @@
from productsearch import config
from productsearch.api.main import api_router
from productsearch.spa import SinglePageApplication
from productsearch.db.redis_helpers import get_async_index


@asynccontextmanager
async def lifespan(app: FastAPI):
index = await get_async_index()
async with index:
yield


app = FastAPI(
title=config.PROJECT_NAME, docs_url=config.API_DOCS, openapi_url=config.OPENAPI_DOCS
Expand Down
1 change: 0 additions & 1 deletion backend/productsearch/tests/api/routes/test_product.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from httpx import AsyncClient

from productsearch.api.schema.product import SimilarityRequest
from productsearch.main import app


@pytest.fixture
Expand Down
11 changes: 4 additions & 7 deletions backend/productsearch/tests/conftest.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
from asyncio import get_event_loop
from typing import Generator

import pytest
import pytest_asyncio
from httpx import AsyncClient
from httpx import AsyncClient, ASGITransport
from redis.asyncio import Redis

from productsearch import config
Expand All @@ -30,7 +27,7 @@ async def client():

@pytest_asyncio.fixture(scope="session")
async def async_client():

async with AsyncClient(app=app, base_url="http://test/api/v1/") as client:

async with AsyncClient(
transport=ASGITransport(app=app), base_url="http://test/api/v1/" # type: ignore
) as client:
yield client
8 changes: 4 additions & 4 deletions backend/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
[tool.poetry]
name = "productsearch"
version = "0.1.0"
version = "0.2.0"
description = "Reference architecture for vector search application with Redis"
authors = ["Robert Shelton <[email protected]>"]
readme = "README.md"
package-mode = false

[tool.poetry.dependencies]
python = "^3.11"
python = ">=3.11,<3.14"
fastapi = "^0.111.0"
uvicorn = "^0.30.1"
ipython = "^8.26.0"
numpy = "1.26.4"
redisvl = "^0.2.3"
redisvl = "^0.4.1"
cohere = "^5.5.8"
openai = "^1.35.9"
sentence-transformers = "^3.0.1"
sentencepiece = "^0.2.0"
redis = "^5.0.7"


[tool.poetry.group.dev.dependencies]
mypy = "1.9.0"
Expand Down
2 changes: 1 addition & 1 deletion docker-local-redis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ services:
depends_on:
- "redis-vector-db"
redis-vector-db:
image: redis/redis-stack:latest
image: redis:8.0-M03
ports:
- "6379:6379"
- "8001:8001"
Expand Down