import httpx import os import time import subprocess import uuid from loguru import logger from typing import Any, List, Tuple, Type, Literal, Optional, Union, Dict import httpx import os import time import subprocess import uuid import streamlit as st from openai import OpenAI import base64 from tqdm import tqdm from app.config import app_settings from app.qdrant_db import MyQdrantClient from app.vdr_utils import ( get_text_embedding, get_image_embedding, pdf_folder_to_images, scale_image, pil_image_to_base64, load_images, ) class VDRSession: def __init__(self): self.client = None self.api_key = None self.base_url = app_settings.GLOBAL_API_BASE self.SAVE_DIR = None self.db_collection = None self.session_id = str(uuid.uuid4())[:5] self.indexed_images = [] self.model_name_list = [] self.vector_db_client = None def set_api_key(self, api_key: str): if api_key is not None and len(api_key)>10: try: api_key = api_key.strip() client = OpenAI(api_key=api_key, base_url=self.base_url) models = client.models.list() if models: self.api_key = api_key self.client = client return True except Exception as e: logger.debug(f'Incorrect API Key: {e}') self.client = None return False def set_context(self, embed_model: str): self.embed_model = embed_model if not self.SAVE_DIR: self.SAVE_DIR=os.path.join('./temp_data', self.session_id) os.makedirs(self.SAVE_DIR, exist_ok=True) self.SAVE_IMAGE_DIR=os.path.join(self.SAVE_DIR, 'images') logger.debug(f'Created folder: {self.SAVE_DIR} and {self.SAVE_IMAGE_DIR}') if not self.vector_db_client: self.vector_db_client = MyQdrantClient(path=self.SAVE_DIR) if not self.db_collection: self.db_collection = f"qd-{embed_model}-{self.session_id}" try: if self.embed_model == "tsi-embedding-colqwen2-2b-v1": self.vector_db_client.create_collection(self.db_collection, vector_dim=128, vector_type="colbert") elif self.embed_model == "jina-embedding-clip-v1": self.vector_db_client.create_collection(self.db_collection, vector_dim=768, vector_type="dense") else: raise ValueError(f"Embedding model {self.embed_model} not supported") except Exception as e: logger.error(f"Error while creating collection: {e}") return True def get_available_vlms(self) -> List[str]: assert self.client != None #model_name_list = [] if self.model_name_list: return self.model_name_list try: models = self.client.models.list() for model in models.data: model_name = model.id substrings = ['gemini-2.0','claude','Qwen2.5-VL-72B-Instruct'] if any(substring in model_name for substring in substrings): self.model_name_list.append(model.id) except Exception as e: logger.error(f"Error while query all models: {e}") raise e # Prioritize name # Remove the item if it exists in the list # priority_item = "gemini-2.0-flash" # if priority_item in model_name_list: # model_name_list.remove(priority_item) # # Insert the item at the beginning of the list # model_name_list.insert(0, priority_item) return self.model_name_list def get_available_image_embeds(self) -> List[str]: assert self.client != None model_name_list = [] try: models = self.client.models.list() for model in models.data: model_name = model.id substrings = ['tsi-embedding','clip'] if any(substring in model_name for substring in substrings): model_name_list.append(model.id) except Exception as e: logger.error(f"Error while query all models: {e}") raise e return model_name_list def search_images(self, text: str, top_k: int = 5) -> list[str]: assert self.client != None assert self.vector_db_client != None try: if not self.indexed_images: raise Exception("No indexed images found. You need to click on 'Add selected context' button to index images.") text = text.strip() if len(text) < 2: return False embeddings = get_text_embedding( texts=text, openai_client=self.client, model=self.embed_model )[0] index_results = self.vector_db_client.query_multivector( multivector_input=embeddings, collection_name=self.db_collection, top_k=top_k ) image_list=[self.indexed_images[i] for i in index_results] images = [] for img in image_list: #with open(file, "rb") as image: #encoded = base64.b64encode(image.read()).decode() encoded = pil_image_to_base64(img) images.append(f"data:image/png;base64,{encoded}") return images except Exception as e: logger.error(f"Error while generating image: {e}") raise e def ask(self, query: str, model: str, prompt_template: str, retrieved_context: Any, modality: str = "image", stream: bool = False) -> str: assert self.client != None assert query != None assert prompt_template != None assert retrieved_context != None try: prompt = prompt_template.format(user_question=query) if modality == "image": context = [ { "type": "image_url", "image_url": { "url": base64_image } } for base64_image in retrieved_context ] content = [ { "type": "text", "text": prompt } ] content=content+context messages=[ { "role": "user", "content": content, } ] chat_response = self.client.chat.completions.create( model=model, messages=messages, temperature=0.1, max_tokens=2048, stream=stream, ) if not stream: return chat_response.choices[0].message.content else: for chunk in chat_response: if chunk.choices: if chunk.choices[0].delta.content is not None: yield chunk.choices[0].delta.content #print(chunk.choices[0].delta.content, end="", flush=True) except Exception as e: logger.error(f"Error while asking: {e}") raise e def indexing(self, uploaded_files: list[str], embed_model: str, indexing_bar: Optional[st.progress] = None) -> bool: self.set_context(embed_model) assert self.client != None assert self.db_collection != None assert self.SAVE_DIR != None assert self.embed_model != None assert len(uploaded_files) > 0 # Write files to disk for file in uploaded_files : path = os.path.join(self.SAVE_DIR, file.name) if os.path.exists(path): print("File existed, skip") continue with open(path, "wb") as f: f.write(file.getvalue()) image_path_list = pdf_folder_to_images(pdf_folder=self.SAVE_DIR, output_folder=self.SAVE_IMAGE_DIR) logger.debug(f"Extracted {len(image_path_list)} images from {len(uploaded_files)} files.") indexed_images = self.index_from_images(image_path_list, indexing_bar=indexing_bar) logger.debug(f"Indexed {len(indexed_images)} images.") self.indexed_images.extend(indexed_images) return True def clear_context(self): self.indexed_images = [] self.vector_db_client.delete_collection(self.db_collection) self.db_collection = None self.vector_db_client = None if self.SAVE_DIR: if os.path.exists(self.SAVE_DIR): subprocess.run(['rm', '-rf', self.SAVE_DIR]) logger.debug(f'Removed folder: {self.SAVE_DIR}') self.SAVE_DIR = None return True def __del__(self): self.clear_context() logger.debug('VDR session is cleaned up.') def index_from_images(self, images_path_list: list, batch_size: int =5, indexing_bar: Optional[st.progress] = None ): try: indexed_images = [] total_len = len(images_path_list) with tqdm(total=total_len, desc="Indexing Progress") as pbar: for i in range(0, total_len, batch_size): try: batch = images_path_list[i:min(i+batch_size,total_len)] #batch = load_images(batch) batch = [scale_image(x, 768) for x in batch] embeddings = get_image_embedding( image_list=batch, openai_client=self.client, model=self.embed_model ) self.vector_db_client.upsert_multivector( index=i, multivector_input_list=embeddings, collection_name=self.db_collection ) indexed_images.extend(batch) # Update the progress bar pbar.update(batch_size) indexing_bar.progress(i/total_len, text=f"Indexing {i}/{total_len}") except Exception as e: logger.exception(f"Error during indexing: {e}") continue return indexed_images logger.debug("Indexing complete!") except Exception as e: raise Exception(f"Error during indexing: {e}")