import os ## replace with Path from pathlib import Path import numpy as np ##SMY import gradio as gr #from watchfiles import run_process ##gradio reload watch from app_gradio_lightrag import LightRAGApp ##SMY lightrag logging from utils.llm_login import get_login_token from utils.file_utils import accumulate_dir import asyncio import nest_asyncio nest_asyncio.apply # import logging, logging.config from dotenv import load_dotenv # Load environment variables load_dotenv() ''' # Pythonic error handling decorator def handle_errors(func): def wrapper(*args, **kwargs): try: return func(*args, **kwargs) except Exception as e: return gr.update(value=f"Error: {e}") return wrapper ''' # Instantiate app logic #app_logic = LightRAGApp() ## See main() # Gradio UI def gradio_ui(app_logic: LightRAGApp): css_icon = """ /* Make reveal button looks like an icon rather than a standard button */ .password-box { position: relative; display: flex; align-items: center; min-width: 30; } .password-box > :first-child { flex-grow: 1; } .icon-button { position: absolute; right: 10px; top: 70%; transform: translateY(-50%); border: none; background: none; color: #4B4B4B; font-size: 1.2em; padding: 0; min-width: 0; box-shadow: none; cursor: pointer; z-index: 100; /* on top */ } """ with gr.Blocks(theme=gr.themes.Soft(), title="SemmyKG - LightRAG Knowledge Graph App", css=css_icon) as gradio_ui: #demo: gr.Markdown(""" # SemmyKG: LightRAG-based Knowledge Graph RAG Upload your markdown docs, index and build a knowledge graph, and query with OpenAI or Ollama. Visualise the KG interactively. """) # Step 0: Section 1 # Define ext type (in lieu of getting from global var) #ext = (".md", "md") #SMY disused: 'tuple' object has no attribute '_id' # Define openai_api textbox initial value openai_api_key_init = os.getenv("OPENAI_API_KEY", "jan-ai") with gr.Sidebar(position="right"): system_prompt_tb = gr.Textbox( value="You are a helpful assistant. You answer questions based on the provided context.", # If you don't know the answer, just say so. Don't make up information.", label="System Prompt", lines=3, interactive=True, show_copy_button=True, ) with gr.Accordion(label="🛞 LLM settings", open=False): with gr.Row(): llm_backend_cb = gr.Radio(["OpenAI", "Ollama", "GenAI"], value="OpenAI", label="LLM Backend: OpenAI, Local or GenAI") llm_model_name_tb = gr.Textbox(value=os.getenv("LLM_MODEL", "openai/gpt-oss-120b"), label="LLM Model Name", show_copy_button=True) #.split('/')[1], label="LLM Model Name") "meta-llama/Llama-4-Maverick-17B-128E-Instruct")), #image-Text-to-Text #"openai/gpt-oss-120b", #with gr.Row(): with gr.Row(): #elem_classes="password-box"): #openai_key_tb = gr.Textbox(value=os.getenv("OPENAI_API_KEY", "jan-ai"), label="OpenAI API Key", # type="password", elem_classes="password-box", container=False, interactive=True, info="OpenAI API Key") #, show_copy_button=True) openai_key_tb = gr.Textbox(value=openai_api_key_init, label="OpenAI API Key", type="password", elem_classes="password-box", container=False, interactive=True, info="OpenAI API Key") #, show_copy_button=True) toggle_btn_openai_key = gr.Button( value="👁️", # Initial eye icon elem_classes="icon-button", size="sm") #, min_width=50) with gr.Row(): openai_baseurl_tb = gr.Textbox(value=os.getenv("OPENAI_API_BASE", "https://router.huggingface.co/v1"), label="OpenAI baseurl", show_copy_button=True) ollama_host_tb = gr.Textbox(value=os.getenv("OLLAMA_HOST", "http://localhost:1234/v1"), label="Ollama Host", show_copy_button=True) #ollama_host_tb = gr.Textbox(value=os.getenv("OPENAI_API_EMBED_BASE", ""), label="Ollama Host") with gr.Row(): embed_backend_dd = gr.Dropdown(choices=["Transformer", "Provider"], value="Transformer", label="Embedding Type") openai_baseurl_embed_tb = gr.Textbox(placeholder=os.getenv("OPENAI_API_EMBED_BASE", "http://localhost:1234/v1"), label="LLM Embed baseurl", show_copy_button=True) with gr.Row(): llm_model_embed_tb = gr.Textbox(placeholder=os.getenv("LLM_MODEL_EMBED","text-embedding-bge-m3"), label="LLM Embedding Model", show_copy_button=True) #.split('/')[1], label="Embedding Model") with gr.Row(): #elem_classes="password-box"): openai_key_embed_tb = gr.Textbox(value=os.getenv("OPENAI_API_KEY_EMBED", "jan-ai"), label="LLM API Key Embed", #lm-studio type="password", elem_classes="password-box", container=False, interactive=True, info="LLM API Key Embed") #, show_copy_button=True) toggle_btn_openai_key_embed = gr.Button( value="👁️", # Initial eye icon elem_classes="icon-button", size="sm") #, min_width=50) #openai_key_embed_tb = gr.Textbox(value=os.getenv("OPENAI_API_KEY_EMBED", "jan-ai"), label="OpenAI API Key Embed", type="password", show_copy_button=True) #("OLLAMA_API_KEY", ""), label="OpenAI API Key Embed", type="password") # Step 1: Section 2 with gr.Row(): with gr.Column(): #data_folder_tb = gr.Textbox(value="dataset/data/docs2", label="Data Folder (markdown only)", show_copy_button=True) dir_btn = gr.UploadButton( #value='dataset/data/', #docs2 #[Errno 13] Permission denied label="📁 Upload Folder", #file_types=ext, #["file"], file_count="directory", ) upload_count_md = gr.Markdown(visible=False) working_dir_tb = gr.Textbox(value="./working_folder1", label="lightRAG working folder", show_copy_button=True) working_dir_reset_cb = gr.Checkbox(value=False, label="Reset working files?") with gr.Accordion("🤗 HuggingFace Client Control", open=True): #, open=False): # HuggingFace controls hf_login_logout_btn = gr.LoginButton( variant="huggingface", value="Sign in to HuggingFace 🤗", logout_value="Logout of HF: ({}) 🤗 \n [NB: check LLM settings & reload files (upload folder)]",) gr.Markdown("---") #gr.HTML("
") setup_btn = gr.Button("Initialise App", variant="primary") status_box = gr.Textbox(label="Status / Progress", interactive=True) #interactive=False) # Step 2: Section 3 gr.HTML("
") #gr.Markdown("---") with gr.Row(): index_btn = gr.Button("Index Documents", interactive=False) stop_btn = gr.Button("Stop", variant="stop") ## Add cancel event button query_text_tb = gr.Textbox(label="Your Query") mode_dd = gr.Dropdown(["naive", "local", "global", "hybrid", "mix"], value="hybrid", label="Query Mode") query_btn = gr.Button("Query") answer_box_md = gr.Markdown(label="Answer") # Step 3: Section 4 kg_btn = gr.Button("Visualise Knowledge Graph") kg_html = gr.HTML(label="Knowledge Graph Visualisation") # Add progress tracking progress_tb = gr.Textbox(label="Progress", interactive=False) ##### Processing ##### ## Note: 1.4.9 query `references` field, `user_prompt` | lightRAG 1.4.0: QueryParam updated. Remove dependency on graspologic # Initialise gr.State ##gr.State component initial value must be able to be deepcopied st_openai_key = gr.State(value=openai_api_key_init) #gr.State("") st_password1 = gr.State(value="password") st_password2 = gr.State(value="password") state_uploaded_file_list = gr.State(value=[]) ### Change handling # Change Handling: update state value def update_state_stored_value(new_component_input): """ Updates stored state for Gradio Component for instance: st_openai_key.value = openai_key_tb Args: new_component_input: New value from component Returns: Updated value for state """ return new_component_input # Change Handling: update Ollama def update_ollama(llm_backend): """ Update LLM settings fields with ollama values""" # Get model name excluding the model provider: # llm_model_name.rpartition("/")[-1] if llm_backend == "Ollama": return { #llm_backend_cb: gr.update(value="Ollama"), llm_model_name_tb: gr.update(value=os.getenv("LLM_MODEL", "meta-llama/Llama-4-Maverick-17B-128E-Instruct").rpartition("/")[-1]), #image-Text-to-Text #"openai/gpt-oss-120b", ##Text-to-Text) #(value="llama2"), openai_key_tb: gr.update(value=os.getenv("OPENAI_API_KEY", "jan-ai"), info="LLM API Key"), openai_baseurl_tb: gr.update(value=os.getenv("OPENAI_API_BASE", "https://router.huggingface.co/v1")), ollama_host_tb: gr.update(value=os.getenv("OLLAMA_HOST", "http://localhost:1234/v1")), #"http://localhost:11434" openai_baseurl_embed_tb: gr.update(value=os.getenv("OPENAI_API_EMBED_BASE", "http://localhost:1234/v1")), #"http://localhost:1234/v1/embeddings" llm_model_embed_tb: gr.update(value=os.getenv("LLM_MODEL_EMBED","nomic-embed-text")), #"nomic-ai/nomic-embed-text-v1.5" openai_key_embed_tb: gr.update(value=os.getenv("OPENAI_API_KEY_EMBED", "jan-ai")) } elif llm_backend == "GenAI": return { llm_model_name_tb: gr.update(value=os.getenv("LLM_MODEL", "google/gemini-2.5-flash-preview-09-2025").rpartition("/")[-1]), #image-Text-to-Text #"google/gemini-2.0-flash-exp:free" #"openai/gpt-oss-120b", ##Text-to-Text) #(value="llama2"), openai_key_tb: gr.update(value=os.getenv("GEMINI_API_KEY", "jan-ai"), info="GenAI API Key"), openai_baseurl_tb: gr.update(value=os.getenv("GEMINI_API_BASE", "https://generativelanguage.googleapis.com/v1beta/openai/"), label="GenAI baaseurl"), ollama_host_tb: gr.update(value=os.getenv("OLLAMA_HOST", "http://localhost:11434")), #"http://localhost:1234/v1" openai_baseurl_embed_tb: gr.update(value=os.getenv("OPENAI_API_EMBED_BASE", "http://localhost:1234/v1")), #"http://localhost:1234/v1/embeddings" llm_model_embed_tb: gr.update(value=os.getenv("LLM_MODEL_EMBED", "nomic-ai/nomic-embed-text-v1.5")), #"all-MiniLM-L6-v2")), openai_key_embed_tb: gr.update(value=os.getenv("OPENAI_API_KEY_EMBED", "jan-ai")) } elif llm_backend == "OpenAI": return { llm_model_name_tb: gr.update(value=os.getenv("LLM_MODEL", "openai/gpt-oss-120b")), #image-Text-to-Text #"openai/gpt-oss-120b", ##Text-to-Text) #(value="llama2"), openai_key_tb: gr.update(value=os.getenv("OPENAI_API_KEY", ""), info="OpenAI API Key"), openai_baseurl_tb: gr.update(value=os.getenv("OPENAI_API_BASE", "https://router.huggingface.co/v1"), label="OpenAI baseurl"), ollama_host_tb: gr.update(value=os.getenv("OLLAMA_HOST", "http://localhost:11434")), #"http://localhost:1234/v1" openai_baseurl_embed_tb: gr.update(value=os.getenv("OPENAI_API_EMBED_BASE", "https://api.openai.com/v1")), #"http://localhost:1234/v1/embeddings" llm_model_embed_tb: gr.update(value=os.getenv("LLM_MODEL_EMBED","text-embedding-3-small")), openai_key_embed_tb: gr.update(value=os.getenv("OPENAI_API_KEY_EMBED", "")) } # Change Handling: update Ollama def update_embedding_backend(embedding_backend): """ Update LLM settings fields with ollama values""" # Get model name excluding the model provider: # llm_model_name.rpartition("/")[-1] if embedding_backend == "Provider": return { openai_baseurl_embed_tb: gr.update(value=os.getenv("OPENAI_API_EMBED_BASE", "http://localhost:1234/v1")), #"http://localhost:1234/v1/embeddings" llm_model_embed_tb: gr.update(value=os.getenv("LLM_MODEL_EMBED","nomic-embed-text")), openai_key_embed_tb: gr.update(value=os.getenv("OPENAI_API_KEY_EMBED", "jan-ai")) } elif embedding_backend == "Transformer": return { openai_baseurl_embed_tb: gr.update(value=None, placeholder=os.getenv("OPENAI_API_EMBED_BASE", "http://localhost:1234/v1")), #"http://localhost:1234/v1/embeddings" llm_model_embed_tb: gr.update(value=None, placeholder=os.getenv("LLM_MODEL_EMBED", "nomic-ai/nomic-embed-text-v1.5")), #(value="all-MiniLM-L6-v2"), openai_key_embed_tb: gr.update(value=None, placeholder="jan-ai") } # Change Handling: Update password reveal state - reusable function for toggling password visibility def toggle_password(current_state): """ Change state Change password input field between visible/hidden Args: current_state: Current password visibility state Returns: Tuple of updates for textbox, button and state """ new_state = "text" if current_state == "password" else "password" new_icon = "👁️" if new_state == "password" else "👁️‍🗨️" # Change icon with state return [ #( gr.update(type=new_state), #gr.Textbox.update(type=new_state), gr.update(value=new_icon), #gr.Button.update(value=new_icon), new_state ] #) # Update gr.State values on HF login change. def custom_do_logout(openai_key, oauth_token: gr.OAuthToken | None=None,): #''' ##SMY: TO DELETE st_openai_key_get = os.getenv("OPENAI_API_KEY", default="") #"" ##SMY: # UnboundLocalError: not catching try: if oauth_token or oauth_token is not None: ##SMY: hack: is not None! st_openai_key_get= update_state_stored_value(oauth_token.token) ##SMY: currently not used optimally except AttributeError: st_openai_key_get= get_login_token(openai_key) #(openai_key_tb) #''' #return gr.update(value="Sign in to HuggingFace 🤗") return gr.update(value="Sign in to HuggingFace 🤗"), gr.update(value=st_openai_key_get) #, gr.update(visible=True, value=msg) #, state_api_token_arg # Button logic with async handling async def setup_wrapper(df, wd, wd_reset, llm_back, embed_back, oai, base, base_embed, model, model_embed, host, embedkey, sys_prompt): return await app_logic.setup(df, wd, wd_reset, llm_back, embed_back, oai, base, base_embed, model, model_embed, host, embedkey, sys_prompt) async def index_wrapper(df): return await app_logic.index_documents(df) async def query_wrapper(q, m): return await app_logic.query(q, m) def stop_wrapper(): ##SMY sync or async """Cancel event wrapper""" app_logic.trigger_cancel() return "Cancellation requested. Awaiting current step to finish..." ### Change handlers llm_backend_cb.change(show_progress="hidden", fn=update_ollama, inputs=llm_backend_cb, #inputs=None, outputs=[llm_model_name_tb, openai_key_tb, openai_baseurl_tb, ollama_host_tb, openai_baseurl_embed_tb, llm_model_embed_tb, openai_key_embed_tb]) embed_backend_dd.change(show_progress="hidden", fn=update_embedding_backend, inputs=embed_backend_dd, outputs=[openai_baseurl_embed_tb,llm_model_embed_tb, openai_key_embed_tb],) ### Button handlers #hf_login_logout_btn.click(update_state_stored_value, inputs=openai_key_tb, outputs=st_openai_key) hf_login_logout_btn.click(fn=custom_do_logout, inputs=openai_key_tb, outputs=[hf_login_logout_btn, st_openai_key]) dir_btn.upload( fn=accumulate_dir, inputs=[dir_btn, state_uploaded_file_list], outputs=[state_uploaded_file_list, index_btn, upload_count_md, status_box], show_progress="hidden" ) toggle_btn_openai_key.click( fn=toggle_password, inputs=[st_password1], outputs=[openai_key_tb, toggle_btn_openai_key, st_password1], show_progress="hidden" ) toggle_btn_openai_key_embed.click( fn=toggle_password, inputs=[st_password2], outputs=[openai_key_embed_tb, toggle_btn_openai_key_embed, st_password2], show_progress="hidden" ) ''' async def setup(self, data_folder: str, working_dir: str, wdir_reset: bool, llm_backend: str, embed_backend: str, openai_key: str, openai_baseurl: str, openai_baseurl_embed: str, llm_model_name: str, llm_model_embed: str, ollama_host: str, embed_key: str, system_prompt: str) -> str: ''' inputs_arg = [state_uploaded_file_list, working_dir_tb, working_dir_reset_cb, llm_backend_cb, embed_backend_dd, st_openai_key, #openai_key_tb, openai_baseurl_tb, openai_baseurl_embed_tb, llm_model_name_tb, llm_model_embed_tb, ollama_host_tb, openai_key_embed_tb, system_prompt_tb] #data_folder_tb, setup_btn.click( fn=setup_wrapper, #inputs=[data_folder_tb, working_dir_tb, llm_backend_cb, openai_key_tb, openai_baseurl_tb, openai_baseurl_embed_tb, llm_model_name_tb, llm_model_embed_tb, ollama_host_tb, openai_key_embed_tb], inputs=inputs_arg, outputs=status_box, show_progress=True ) index_btn.click( fn=index_wrapper, inputs=state_uploaded_file_list, #[data_folder_tb], outputs=[status_box, progress_tb], show_progress=True ) query_btn.click( fn=query_wrapper, inputs=[query_text_tb, mode_dd], outputs=answer_box_md ) kg_btn.click( fn=app_logic.show_kg, inputs=None, outputs=kg_html, show_progress=True ) stop_btn.click( fn=stop_wrapper, inputs=[], outputs=[status_box] ) return gradio_ui if __name__ == "__main__": #gradio_ui().launch() ##SMY: assist: https://www.gradio.app/guides/developing-faster-with-reload-mode ##SMY: NB: gradio app_gradio_lightrag.py --demo-name=gradio_ui async def main(): from app_gradio_lightrag import LightRAGApp # Instantiate LightRAG and launch Gradio try: app_logic = LightRAGApp() # Launch Gradio with queue enable to enable >60s timeout gradio_ui(app_logic).queue().launch() #(server_port=7866) except Exception as e: print(f"An error occurred: {e}") finally: if app_logic.rag: await app_logic.rag.finalize_storages() from utils.logger import get_logger, setup_logging setup_logging() ## set logging logger_kg = get_logger("semmyKG") ## app logging ##SMY Initialise logging before running the main function: See lightrag_openai_compatible_demo.py from app_gradio_lightrag import handle_errors, configure_logging configure_logging() ## lightRAG logging asyncio.run(main()) ##SMY: gradio reload-mode watch: https://github.com/huggingface/smolagents/issues/789 #run_process(".", target=gradio_ui)