| import streamlit as st |
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
| import pickle |
| import numpy as np |
| import re |
| from sklearn.cluster import KMeans |
| from transformers import T5Tokenizer, T5EncoderModel |
| import os |
| from dotenv import load_dotenv |
| import google.generativeai as genai |
| from bio_clip_recommender import BioCLIP, ProteinEmbedder, ProtocolRecommender |
|
|
| load_dotenv() |
| GEMINI_API_KEY = os.getenv("GEMINI_API_KEY") |
|
|
| if not GEMINI_API_KEY: |
| st.error("API Key not found! Please check that your .env file exists and is formatted correctly.") |
| st.stop() |
|
|
| genai.configure(api_key=GEMINI_API_KEY) |
|
|
| sys_instruct = """ |
| You are a Senior Research Biochemist. Your primary role is to design scientifically rigorous, highly cohesive protein purification pipelines based ONLY on provided laboratory data. |
| |
| Follow these strict guidelines: |
| 1. Tone & Style: Be concise, professional, and direct. Omit conversational filler and pleasantries. |
| 2. Formatting: Use standard Markdown. Create clear headers for each step (e.g., '### Step 1: Lysis'). Bold all specific buffer concentrations, proteins, reagents, and pH values so they are easy to read at the bench. |
| 3. Biochemical Rationale: For every step, explicitly state *why* specific reagents are used based on standard biochemical principles. |
| 4. Chemical Guardrails: Never invent or hallucinate protocols or buffers. If you detect chemical incompatibilities in the user's request or retrieved data (e.g., high DTT concentrations applied to standard Ni-NTA columns, or inappropriate detergents for soluble proteins), explicitly flag them with a bold **WARNING**. |
| """ |
|
|
| model = genai.GenerativeModel('gemini-3.5-flash', |
| system_instruction=sys_instruct) |
|
|
| st.set_page_config( |
| page_title="Bio-CLIP Recommender", |
| layout="wide", |
| initial_sidebar_state="expanded" |
| ) |
|
|
| @st.cache_resource(show_spinner=False) |
| def load_ai_engines(): |
| device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
| embedder = ProteinEmbedder(device=device) |
| recommender = ProtocolRecommender(device=device) |
| return embedder, recommender |
|
|
| st.title("Bio-CLIP: Protein Purification AI") |
| st.markdown("Zero-shot protocol recommendation directly from 1D amino acid sequences.") |
|
|
| with st.spinner("Booting up ProtT5 and Bio-CLIP Models (this takes a moment)..."): |
| try: |
| embedder, recommender = load_ai_engines() |
| models_loaded = True |
| except Exception as e: |
| st.error(f"Failed to load model weights. Ensure `.pth` and `.pkl` files are in the same folder. \n\nError: {e}") |
| models_loaded = False |
|
|
| if models_loaded: |
| with st.sidebar: |
| st.header("About") |
| st.write("This tool uses **ProtT5** to embed amino acids and a Contrastive Learning (**Bio-CLIP**) model to map them to historical purification protocols.") |
| st.markdown("---") |
| st.write("**Top K Results**") |
| top_k = st.slider("Number of recommendations:", 1, 5, 3) |
|
|
| st.markdown("### Input Sequence") |
| default_gfp = "MSKGEELFTGVVPILVELDGDVNGHKFSVSGEGEGDATYGKLTLKFICTTGKLPVPWPTLVTTFSYGVQCFSRYPDHMKQHDFFKSAMPEGYVQERTIFFKDDGNYKTRAEVKFEGDTLVNRIELKGIDFKEDGNILGHKLEYNYNSHNVYIMADKQKNGIKVNFKIRHNIEDGSVQLADHYQQNTPIGDGPVLLPDNHYLSTQSALSKDPNEKRDHMVLLEFVTAAGITHGMDELYK" |
| sequence_input = st.text_area("Paste Amino Acid Sequence here:", value=default_gfp, height=150) |
|
|
| user_goal = st.text_input("Optional: What is your specific purification goal? (e.g., 'Prioritize highest yield', 'Are there temperature concerns?')") |
|
|
| if st.button("Generate Purification Pipeline", type="primary"): |
| sequence_input = sequence_input.strip() |
| |
| if len(sequence_input) < 10: |
| st.warning("Please enter a valid amino acid sequence (at least 10 characters).") |
| else: |
| with st.spinner("1/2: Running ProtT5 Sequence Embedding..."): |
| seq_vector = embedder.embed_raw_sequence(sequence_input) |
| |
| with st.spinner("2/2: Querying Bio-CLIP Latent Space..."): |
| results = recommender.search(seq_vector, top_k=top_k) |
| |
| st.success("Search Complete!") |
| st.markdown("---") |
| |
| col1, col2, col3 = st.columns(3) |
| |
| columns = {'lysis': col1, 'elution': col2, 'desalting': col3} |
| |
| for step in ['lysis', 'elution', 'desalting']: |
| with columns[step]: |
| st.subheader(f"{step.capitalize()}") |
| for i, res in enumerate(results[step]): |
| |
| conf = max(0.0, min(1.0, (res['confidence'] + 0.1))) |
| st.markdown(f"**Option {i+1}**") |
| st.progress(float(conf), text=f"Confidence: {conf*100:.1f}%") |
| st.info(res['text']) |
| st.write("") |
|
|
| st.markdown("---") |
|
|
| st.markdown("### 3. AI Synthesis") |
| with st.spinner("3/3: Gemini AI analyzing retrieved protocols..."): |
| try: |
| retrieved_context = f""" |
| Lysis Options: {[res['text'] for res in results['lysis']]} |
| Elution Options: {[res['text'] for res in results['elution']]} |
| Desalting Options: {[res['text'] for res in results['desalting']]} |
| """ |
| has_his_tag = "HHHHHH" in sequence_input |
|
|
| rag_prompt = f""" |
| |
| User Sequence: {sequence_input} |
| Contains His-Tag: {has_his_tag} |
| |
| Synthesize a cohesive, step-by-step protein purification protocol using ONLY the retrieved options below. |
| If the user provided a specific goal, tailor the synthesis to that goal. |
| |
| User's specific goal/question: {user_goal if user_goal else "Provide a standard, recommended cohesive pipeline from these options."} |
| |
| --- RETRIEVED PROTOCOL OPTIONS --- |
| {retrieved_context} |
| ---------------------------------- |
| """ |
|
|
| response = model.generate_content(rag_prompt) |
| |
| st.write(response.text) |
| |
| except Exception as e: |
| st.error(f"An error occurred while generating the AI summary: {e}") |