Merge pull request #150 from presenton/fix/embedding-issue-in-arm

fix/embedding issue in arm
This commit is contained in:
Saurav Niraula 2025-07-28 15:17:49 +05:45 committed by GitHub
commit cd327451c0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
11 changed files with 148 additions and 583680 deletions

View file

@ -30,28 +30,41 @@ http {
proxy_connect_timeout 30m;
}
location /static {
proxy_pass http://localhost:8000;
proxy_read_timeout 30m;
proxy_connect_timeout 30m;
}
location /app_data {
proxy_pass http://localhost:8000;
proxy_read_timeout 30m;
proxy_connect_timeout 30m;
}
location /docs {
proxy_pass http://localhost:8000/docs;
proxy_read_timeout 30m;
proxy_connect_timeout 30m;
}
location /openapi.json {
proxy_pass http://localhost:8000/openapi.json;
proxy_read_timeout 30m;
proxy_connect_timeout 30m;
}
# Static
location /static {
alias /app/servers/fastapi/static/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location /app_data/images/ {
alias /app_data/images/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location /app_data/exports/ {
alias /app_data/exports/;
expires 1y;
add_header Cache-Control "public, immutable";
}
location /app_data/uploads/ {
alias /app_data/uploads/;
expires 1y;
add_header Cache-Control "public, immutable";
}
}
}

View file

@ -22,6 +22,8 @@ from models.generate_presentation_api import (
PresentationPathAndEditPath,
)
from services.get_layout_by_name import get_layout_by_name
from services.icon_finder_service import IconFinderService
from services.image_generation_service import ImageGenerationService
from utils.llm_calls.generate_presentation_outlines import generate_ppt_outline
from models.sql.slide import SlideModel
from models.sse_response import SSECompleteResponse, SSEResponse
@ -30,7 +32,7 @@ from services.database import get_sql_session
from services.documents_loader import DocumentsLoader
from models.sql.presentation import PresentationModel
from services.pptx_presentation_creator import PptxPresentationCreator
from utils.asset_directory_utils import get_exports_directory
from utils.asset_directory_utils import get_exports_directory, get_images_directory
from utils.llm_calls.generate_document_summary import generate_document_summary
from utils.llm_calls.generate_presentation_structure import (
generate_presentation_structure,
@ -195,6 +197,9 @@ async def stream_presentation(presentation_id: str):
detail="Outlines can not be empty",
)
image_generation_service = ImageGenerationService(get_images_directory())
icon_finder_service = IconFinderService()
async def inner():
structure = presentation.get_structure()
layout = presentation.get_layout()
@ -223,7 +228,11 @@ async def stream_presentation(presentation_id: str):
slides.append(slide)
# This will mutate slide
async_assets_generation_tasks.append(process_slide_and_fetch_assets(slide))
async_assets_generation_tasks.append(
process_slide_and_fetch_assets(
image_generation_service, icon_finder_service, slide
)
)
# Give control to the event loop
await asyncio.sleep(0)
@ -423,9 +432,13 @@ async def generate_presentation_api(
# Process slides to fetch assets (images, icons, etc.)
print("Processing slides to fetch assets")
image_generation_service = ImageGenerationService(get_images_directory())
icon_finder_service = IconFinderService()
for slide in slides:
try:
await process_slide_and_fetch_assets(slide)
await process_slide_and_fetch_assets(
image_generation_service, icon_finder_service, slide
)
print(f"Processed slide {slide.index} successfully")
except Exception as e:
print(f"Error processing slide {slide.index}: {e}")

View file

@ -4,6 +4,9 @@ from fastapi import APIRouter, Body, HTTPException
from models.sql.presentation import PresentationModel
from models.sql.slide import SlideModel
from services.database import get_sql_session
from services.icon_finder_service import IconFinderService
from services.image_generation_service import ImageGenerationService
from utils.asset_directory_utils import get_images_directory
from utils.llm_calls.edit_slide import get_edited_slide_content
from utils.llm_calls.edit_slide_html import get_edited_slide_html
from utils.llm_calls.select_slide_type_on_edit import get_slide_layout_from_prompt
@ -34,9 +37,15 @@ async def edit_slide(id: Annotated[str, Body()], prompt: Annotated[str, Body()])
prompt, slide_layout, slide, presentation.language
)
image_generation_service = ImageGenerationService(get_images_directory())
icon_finder_service = IconFinderService()
# This will mutate edited_slide_content
new_assets = await process_old_and_new_slides_and_fetch_assets(
slide.content, edited_slide_content
image_generation_service,
icon_finder_service,
slide.content,
edited_slide_content,
)
# Always assign a new unique id to the slide

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1 @@
tmp lock file

Binary file not shown.

View file

@ -0,0 +1 @@
{"collections": {"icons": {"vectors": {"fast-bge-small-en-v1.5": {"size": 384, "distance": "Cosine", "hnsw_config": null, "quantization_config": null, "on_disk": null, "datatype": null, "multivector_config": null}}, "shard_number": null, "sharding_method": null, "replication_factor": null, "write_consistency_factor": null, "on_disk_payload": null, "hnsw_config": null, "wal_config": null, "optimizers_config": null, "init_from": null, "quantization_config": null, "sparse_vectors": null, "strict_mode_config": null}}, "aliases": {}}

View file

@ -5,70 +5,120 @@ annotated-types==0.7.0
anyio==4.9.0
async-timeout==5.0.1
attrs==25.3.0
backoff==2.2.1
bcrypt==4.3.0
build==1.2.2.post1
cachetools==5.5.2
certifi==2025.7.14
cffi==1.17.1
charset-normalizer==3.4.2
click==8.2.1
coloredlogs==15.0.1
cryptography==45.0.5
distro==1.9.0
dnspython==2.7.0
durationpy==0.10
email_validator==2.2.0
fastapi==0.116.1
fastapi-cli==0.0.8
fastapi-cloud-cli==0.1.4
fastembed_vectorstore==0.1.6
fastembed==0.7.1
filelock==3.18.0
flatbuffers==25.2.10
frozenlist==1.7.0
fsspec==2025.7.0
google-auth==2.40.3
google-genai==1.25.0
googleapis-common-protos==1.70.0
greenlet==3.2.3
grpcio==1.74.0
h11==0.16.0
h2==4.2.0
hf-xet==1.1.5
hpack==4.1.0
httpcore==1.0.9
httptools==0.6.4
httpx==0.28.1
huggingface-hub==0.34.1
humanfriendly==10.0
hyperframe==6.1.0
idna==3.10
importlib_metadata==8.7.0
importlib_resources==6.5.2
iniconfig==2.1.0
Jinja2==3.1.6
jiter==0.10.0
jsonschema==4.25.0
jsonschema-specifications==2025.4.1
kubernetes==33.1.0
loguru==0.7.3
lxml==6.0.0
markdown-it-py==3.0.0
MarkupSafe==3.0.2
mdurl==0.1.2
mmh3==5.1.0
mpmath==1.3.0
multidict==6.6.3
numpy==2.3.2
oauthlib==3.3.1
onnxruntime==1.22.1
openai==1.95.1
opentelemetry-api==1.35.0
opentelemetry-exporter-otlp-proto-common==1.35.0
opentelemetry-exporter-otlp-proto-grpc==1.35.0
opentelemetry-proto==1.35.0
opentelemetry-sdk==1.35.0
opentelemetry-semantic-conventions==0.56b0
orjson==3.11.1
overrides==7.7.0
packaging==25.0
pathvalidate==3.3.1
pdfminer.six==20250506
pdfplumber==0.11.7
pillow==11.3.0
pluggy==1.6.0
portalocker==3.2.0
posthog==5.4.0
propcache==0.3.2
protobuf==6.31.1
py_rust_stemmers==0.1.5
pyasn1==0.6.1
pyasn1_modules==0.4.2
pybase64==1.4.2
pycparser==2.22
pydantic==2.11.7
pydantic_core==2.33.2
Pygments==2.19.2
pypdfium2==4.30.1
PyPika==0.48.9
pyproject_hooks==1.2.0
pytest==8.4.1
python-dateutil==2.9.0.post0
python-docx==1.2.0
python-dotenv==1.1.1
python-multipart==0.0.20
python-pptx==1.0.2
PyYAML==6.0.2
qdrant-client==1.15.0
redis==6.2.0
referencing==0.36.2
requests==2.32.4
requests-oauthlib==2.0.0
rich==14.0.0
rich-toolkit==0.14.8
rignore==0.6.2
rpds-py==0.26.0
rsa==4.9.1
sentry-sdk==2.32.0
shellingham==1.5.4
six==1.17.0
sniffio==1.3.1
SQLAlchemy==2.0.41
sqlmodel==0.0.24
starlette==0.47.1
sympy==1.14.0
tenacity==8.5.0
tokenizers==0.21.2
tqdm==4.67.1
typer==0.16.0
typing-inspection==0.4.1
@ -77,6 +127,8 @@ urllib3==2.5.0
uvicorn==0.35.0
uvloop==0.21.0
watchfiles==1.1.0
websocket-client==1.8.0
websockets==15.0.1
xlsxwriter==3.2.5
yarl==1.20.1
zipp==3.23.0

View file

@ -1,7 +1,8 @@
import os
import uvicorn
import argparse
from services.icon_finder_service import IconFinderService
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Run the FastAPI server")
@ -13,6 +14,9 @@ if __name__ == "__main__":
)
args = parser.parse_args()
# Initialize the Icons Collection
IconFinderService()
uvicorn.run(
"api.main:app",
host="0.0.0.0",

View file

@ -1,35 +1,48 @@
import asyncio
import json
import os
from fastembed_vectorstore import FastembedVectorstore, FastembedEmbeddingModel
from qdrant_client import QdrantClient
class IconFinderService:
def __init__(self):
self.vector_store = self.get_icons_vectorstore()
self.collection_name = "icons"
self.client = QdrantClient(path="qdrant")
print("Initializing icons collection...")
self.client.set_model("BAAI/bge-small-en-v1.5")
self._initialize_icons_collection()
print("Icons collection initialized")
def get_icons_vectorstore(self):
vector_store_path = "assets/icons_vectorstore.json"
embedding_model = FastembedEmbeddingModel.BGESmallENV15
def _initialize_icons_collection(self):
try:
self.client.get_collection(self.collection_name)
except Exception:
self._populate_icons_collection()
if os.path.exists(vector_store_path):
return FastembedVectorstore.load(embedding_model, vector_store_path)
vector_store = FastembedVectorstore(embedding_model)
def _populate_icons_collection(self):
with open("assets/icons.json", "r") as f:
icons = json.load(f)
documents = []
metadata = []
for each in icons["icons"]:
if each["name"].split("-")[-1] == "bold":
documents.append(f"{each['name']}||{each['tags']}")
doc_text = f"{each['name']} {each['tags']}"
documents.append(doc_text)
metadata.append({"name": each["name"]})
vector_store.embed_documents(documents)
vector_store.save(vector_store_path)
return vector_store
if documents:
self.client.add(
collection_name=self.collection_name,
documents=documents,
metadata=metadata,
)
async def search_icons(self, query: str, k: int = 1):
result = await asyncio.to_thread(self.vector_store.search, query, k)
return [
f"/static/icons/bold/{result[0].split('||')[0]}.png" for result in result
]
result = await asyncio.to_thread(
self.client.query,
collection_name=self.collection_name,
query_text=query,
limit=k,
)
return [f"/static/icons/bold/{each.metadata['name']}.png" for each in result]

View file

@ -10,12 +10,10 @@ from utils.dict_utils import get_dict_at_path, get_dict_paths_with_key, set_dict
async def process_slide_and_fetch_assets(
image_generation_service: ImageGenerationService,
icon_finder_service: IconFinderService,
slide: SlideModel,
) -> List[ImageAsset]:
image_directory = get_images_directory()
image_generation_service = ImageGenerationService(image_directory)
icon_finder_service = IconFinderService()
async_tasks = []
@ -61,6 +59,8 @@ async def process_slide_and_fetch_assets(
async def process_old_and_new_slides_and_fetch_assets(
image_generation_service: ImageGenerationService,
icon_finder_service: IconFinderService,
old_slide_content: dict,
new_slide_content: dict,
) -> List[ImageAsset]:
@ -98,10 +98,6 @@ async def process_old_and_new_slides_and_fetch_assets(
get_dict_at_path(new_slide_content, path) for path in new_icon_dict_paths
]
# Creates services
image_generation_service = ImageGenerationService(get_images_directory())
icon_finder_service = IconFinderService()
# Creates async tasks for fetching new images
async_image_fetch_tasks = []
new_images_fetch_status = []