Spaces:
Sleeping
Sleeping
| """ | |
| Adaptive context engine. | |
| Tracks user session history, infers expertise level, and generates | |
| context-aware system prompt extensions for the custom GPT. | |
| """ | |
| from __future__ import annotations | |
| import time | |
| from collections import defaultdict | |
| from dataclasses import dataclass, field | |
| from typing import Any | |
| # ββ In-memory session store (per HF Space instance) βββββββββββββββββββββββββββ | |
| class Session: | |
| session_id: str | |
| created_at: float = field(default_factory=time.time) | |
| proteins_visited: list[str] = field(default_factory=list) | |
| question_complexity: list[int] = field(default_factory=list) | |
| domains_of_interest: list[str] = field(default_factory=list) | |
| interaction_count: int = 0 | |
| expertise_score: float = 0.5 # 0=novice, 1=expert | |
| preferred_depth: str = "balanced" # brief | balanced | deep | |
| _sessions: dict[str, Session] = {} | |
| def get_session(session_id: str) -> Session: | |
| if session_id not in _sessions: | |
| _sessions[session_id] = Session(session_id=session_id) | |
| return _sessions[session_id] | |
| # ββ Expertise inference ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| EXPERT_TERMS = { | |
| "plddt", "alphafold", "uniprot", "pfam", "ptm", "idr", "cryo-em", | |
| "x-ray crystallography", "ramachandran", "secondary structure", | |
| "disordered region", "binding affinity", "allosteric", "catalytic triad", | |
| "homology", "ortholog", "paralog", "domain", "residue", "motif", | |
| "b-factor", "resolution", "rmsd", "torsion angle", | |
| } | |
| NOVICE_TERMS = { | |
| "what is", "explain", "how does", "simple", "basic", "beginner", | |
| "tell me about", "meaning of", "what does", | |
| } | |
| def _infer_complexity(query: str) -> int: | |
| """Return 0 (novice) to 2 (expert) based on query vocabulary.""" | |
| q = query.lower() | |
| expert_hits = sum(1 for t in EXPERT_TERMS if t in q) | |
| novice_hits = sum(1 for t in NOVICE_TERMS if t in q) | |
| if expert_hits >= 2: | |
| return 2 | |
| if expert_hits == 1: | |
| return 1 | |
| if novice_hits > 0: | |
| return 0 | |
| return 1 | |
| # ββ Adaptive system prompt builder ββββββββββββββββββββββββββββββββββββββββββββ | |
| DEPTH_INSTRUCTIONS = { | |
| "brief": ( | |
| "Keep responses concise. Use plain language. " | |
| "Avoid jargon. Focus on practical takeaways." | |
| ), | |
| "balanced": ( | |
| "Balance scientific accuracy with clarity. " | |
| "Define technical terms on first use. " | |
| "Provide one concrete example per concept." | |
| ), | |
| "deep": ( | |
| "Assume expert-level structural biology knowledge. " | |
| "Include mechanistic detail, cite pLDDT thresholds, " | |
| "reference domain databases (Pfam, InterPro), and discuss " | |
| "functional implications at the residue level." | |
| ), | |
| } | |
| def build_adaptive_prompt(session: Session, current_query: str = "") -> str: | |
| """Return a context block that the GPT prepends to its system prompt.""" | |
| if current_query: | |
| c = _infer_complexity(current_query) | |
| session.question_complexity.append(c) | |
| session.interaction_count += 1 | |
| # Exponential moving average for expertise score | |
| alpha = 0.3 | |
| session.expertise_score = ( | |
| alpha * (c / 2.0) + (1 - alpha) * session.expertise_score | |
| ) | |
| # Decide depth | |
| if session.expertise_score > 0.65: | |
| session.preferred_depth = "deep" | |
| elif session.expertise_score < 0.35: | |
| session.preferred_depth = "brief" | |
| else: | |
| session.preferred_depth = "balanced" | |
| depth_instr = DEPTH_INSTRUCTIONS[session.preferred_depth] | |
| # Build context block | |
| history_line = "" | |
| if session.proteins_visited: | |
| recent = session.proteins_visited[-5:] | |
| history_line = f"Proteins discussed this session: {', '.join(recent)}." | |
| domain_line = "" | |
| if session.domains_of_interest: | |
| domains = list(set(session.domains_of_interest[-6:])) | |
| domain_line = f"User's areas of interest: {', '.join(domains)}." | |
| prompt = f"""[ADAPTIVE CONTEXT β session {session.session_id[:8]}] | |
| Detected expertise level: {session.preferred_depth.upper()} ({session.expertise_score:.2f}/1.0). | |
| Instruction: {depth_instr} | |
| {history_line} | |
| {domain_line} | |
| Interaction count this session: {session.interaction_count}. | |
| Tailor every response to the above profile without explicitly mentioning it.""" | |
| return prompt.strip() | |
| def update_session_protein(session: Session, uniprot_id: str, domains: list[str]) -> None: | |
| if uniprot_id not in session.proteins_visited: | |
| session.proteins_visited.append(uniprot_id) | |
| session.domains_of_interest.extend(domains) | |
| # ββ Adaptive follow-up question generator ββββββββββββββββββββββββββββββββββββ | |
| _FOLLOWUPS: dict[str, list[str]] = { | |
| "novice": [ | |
| "What does this protein actually do in the human body?", | |
| "Is this protein associated with any disease?", | |
| "What happens if this protein is mutated?", | |
| "Can you explain what the confidence colours mean?", | |
| ], | |
| "balanced": [ | |
| "Which residues form the active site or binding interface?", | |
| "Are there known pathogenic mutations in this protein?", | |
| "What structural domains does this protein contain?", | |
| "How does pLDDT score vary across regions β and what does that imply?", | |
| ], | |
| "expert": [ | |
| "What is the allosteric communication network within this structure?", | |
| "Which disordered regions are functionally important?", | |
| "How does the predicted structure compare to experimental PDB entries?", | |
| "What post-translational modifications are predicted at high-pLDDT residues?", | |
| ], | |
| } | |
| def generate_followups(session: Session, n: int = 3) -> list[str]: | |
| level = session.preferred_depth | |
| key = {"brief": "novice", "balanced": "balanced", "deep": "expert"}[level] | |
| pool = _FOLLOWUPS[key] | |
| visited = len(session.proteins_visited) | |
| start = (visited * 2) % len(pool) | |
| return [pool[(start + i) % len(pool)] for i in range(n)] | |