azlaan428 commited on
Commit
fe7e528
·
1 Parent(s): d1f5505

feat: Flask web UI + README

Browse files
Files changed (6) hide show
  1. README.md +54 -1
  2. agent/agent.py +13 -2
  3. app.py +35 -0
  4. static/script.js +49 -0
  5. static/style.css +153 -0
  6. templates/index.html +40 -0
README.md CHANGED
@@ -1 +1,54 @@
1
- # glitch-squad-biomedical-assistant
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Glitch Squad Biomedical Assistant
2
+
3
+ A biomedical AI research assistant that retrieves live literature from PubMed and synthesises responses using a LangGraph ReAct agent powered by Llama 3.2.
4
+
5
+ Built for the AMD Developer Hackathon 2026.
6
+
7
+ ---
8
+
9
+ ## What It Does
10
+
11
+ You type a biomedical research question. The agent autonomously searches PubMed for relevant abstracts, reasons over the retrieved literature, and returns a synthesised answer with PMID citations.
12
+
13
+ ---
14
+
15
+ ## Tech Stack
16
+
17
+ | Component | Technology |
18
+ |---|---|
19
+ | LLM | Llama 3.2 via Ollama (local) / Mistral 7B on AMD MI300X (cloud) |
20
+ | Agent Framework | LangGraph prebuilt ReAct agent |
21
+ | Literature Retrieval | BioPython Entrez / NCBIPubMed API |
22
+ | Web Framework | Flask |
23
+ | Frontend | HTML, CSS, JavaScript |
24
+ | Runtime | Python 3.12, WSL2 Ubuntu 24.04 |
25
+
26
+ ---
27
+
28
+ ## How To Run Locally
29
+
30
+ ### Prerequisites
31
+ - Python 3.12
32
+ - Ollama installed
33
+ - llama3.2 model pulled: `ollama pull llama3.2`
34
+
35
+ ### Setup
36
+
37
+ git clone https://github.com/azlaan428/glitch-squad-biomedical-assistant.git
38
+ cd glitch-squad-biomedical-assistant
39
+ python3 -m venv _venv
40
+ source venv/bin/activate
41
+ pip install -r requirements.txt
42
+
43
+ ### Run
44
+
45
+ ollama serve &
46
+ python app.py
47
+
48
+ Open your browser at http://localhost:5000
49
+
50
+ ---
51
+
52
+ ## Team
53
+
54
+ **Glitch Squad** -- Ziauddin University, Karachi, Pakistan
agent/agent.py CHANGED
@@ -4,6 +4,7 @@ sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
4
  from langchain_core.tools import Tool
5
  from langchain_ollama import ChatOllama
6
  from langgraph.prebuilt import create_react_agent
 
7
  from retrieval.pubmed import fetch_pubmed
8
 
9
 
@@ -23,10 +24,20 @@ pubmed_tool = Tool(
23
  )
24
  )
25
 
 
 
 
 
 
 
 
 
 
 
 
26
  if __name__ == "__main__":
27
  print("Connecting to Ollama...")
28
- llm = ChatOllama(model="llama3.2", temperature=0)
29
- agent = create_react_agent(llm, [pubmed_tool])
30
  query = "What ML methods are used for epilepsy seizure detection?"
31
  print(f"\nQuery: {query}\n")
32
  result = agent.invoke({"messages": [{"role": "user", "content": query}]})
 
4
  from langchain_core.tools import Tool
5
  from langchain_ollama import ChatOllama
6
  from langgraph.prebuilt import create_react_agent
7
+ from langchain_core.messages import SystemMessage
8
  from retrieval.pubmed import fetch_pubmed
9
 
10
 
 
24
  )
25
  )
26
 
27
+ SYSTEM_PROMPT = """You are a biomedical research assistant. When given a question:
28
+ 1. Use the PubMedSearch tool to retrieve relevant literature
29
+ 2. Read the retrieved abstracts carefully
30
+ 3. Answer the user's specific question directly based on what the abstracts say
31
+ 4. Cite the PMID numbers of the papers you reference
32
+ 5. Do not summarise unrelated papers — only answer what was asked"""
33
+
34
+ def build_agent():
35
+ llm = ChatOllama(model="llama3.2", temperature=0)
36
+ return create_react_agent(llm, [pubmed_tool], prompt=SYSTEM_PROMPT)
37
+
38
  if __name__ == "__main__":
39
  print("Connecting to Ollama...")
40
+ agent = build_agent()
 
41
  query = "What ML methods are used for epilepsy seizure detection?"
42
  print(f"\nQuery: {query}\n")
43
  result = agent.invoke({"messages": [{"role": "user", "content": query}]})
app.py ADDED
@@ -0,0 +1,35 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys, os
2
+ sys.path.append(os.path.dirname(os.path.abspath(__file__)))
3
+
4
+ from flask import Flask, render_template, request, jsonify
5
+ from agent.agent import build_agent
6
+
7
+ app = Flask(__name__)
8
+ agent_executor = None
9
+
10
+ def get_agent():
11
+ global agent_executor
12
+ if agent_executor is None:
13
+ agent_executor = build_agent()
14
+ return agent_executor
15
+
16
+ @app.route("/")
17
+ def index():
18
+ return render_template("index.html")
19
+
20
+ @app.route("/query", methods=["POST"])
21
+ def query():
22
+ data = request.get_json()
23
+ user_query = data.get("query", "").strip()
24
+ if not user_query:
25
+ return jsonify({"error": "Empty query"}), 400
26
+ try:
27
+ agent = get_agent()
28
+ result = agent.invoke({"messages": [{"role": "user", "content": user_query}]})
29
+ response = result["messages"][-1].content
30
+ return jsonify({"response": response})
31
+ except Exception as e:
32
+ return jsonify({"error": str(e)}), 500
33
+
34
+ if __name__ == "__main__":
35
+ app.run(debug=True, port=5000)
static/script.js ADDED
@@ -0,0 +1,49 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ async function submitQuery() {
2
+ const input = document.getElementById('queryInput');
3
+ const btn = document.getElementById('submitBtn');
4
+ const statusBar = document.getElementById('statusBar');
5
+ const statusText = document.getElementById('statusText');
6
+ const responseBox = document.getElementById('responseBox');
7
+ const responseText = document.getElementById('responseText');
8
+
9
+ const query = input.value.trim();
10
+ if (!query) return;
11
+
12
+ btn.disabled = true;
13
+ responseBox.classList.add('hidden');
14
+ statusBar.classList.remove('hidden');
15
+ statusText.textContent = 'Retrieving PubMed literature...';
16
+
17
+ try {
18
+ const res = await fetch('/query', {
19
+ method: 'POST',
20
+ headers: { 'Content-Type': 'application/json' },
21
+ body: JSON.stringify({ query: query })
22
+ });
23
+
24
+ const data = await res.json();
25
+ statusBar.classList.add('hidden');
26
+
27
+ if (data.error) {
28
+ responseText.textContent = 'Error: ' + data.error;
29
+ } else {
30
+ responseText.textContent = data.response;
31
+ }
32
+
33
+ responseBox.classList.remove('hidden');
34
+
35
+ } catch (err) {
36
+ statusBar.classList.add('hidden');
37
+ responseText.textContent = 'Network error: ' + err.message;
38
+ responseBox.classList.remove('hidden');
39
+ } finally {
40
+ btn.disabled = false;
41
+ }
42
+ }
43
+
44
+ document.addEventListener('keydown', function(e) {
45
+ if (e.key === 'Enter' && !e.shiftKey) {
46
+ e.preventDefault();
47
+ submitQuery();
48
+ }
49
+ });
static/style.css ADDED
@@ -0,0 +1,153 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ * {
2
+ margin: 0;
3
+ padding: 0;
4
+ box-sizing: border-box;
5
+ }
6
+
7
+ body {
8
+ background-color: #0a0a0f;
9
+ color: #e0e0e0;
10
+ font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
11
+ min-height: 100vh;
12
+ display: flex;
13
+ align-items: center;
14
+ justify-content: center;
15
+ }
16
+
17
+ .container {
18
+ width: 100%;
19
+ max-width: 860px;
20
+ padding: 40px 24px;
21
+ }
22
+
23
+ header {
24
+ text-align: center;
25
+ margin-bottom: 48px;
26
+ }
27
+
28
+ header h1 {
29
+ font-size: 2.8rem;
30
+ font-weight: 700;
31
+ color: #00c896;
32
+ letter-spacing: 2px;
33
+ text-transform: uppercase;
34
+ }
35
+
36
+ header .subtitle {
37
+ font-size: 1rem;
38
+ color: #888;
39
+ margin-top: 8px;
40
+ letter-spacing: 1px;
41
+ }
42
+
43
+ .search-box {
44
+ display: flex;
45
+ flex-direction: column;
46
+ gap: 12px;
47
+ }
48
+
49
+ textarea {
50
+ width: 100%;
51
+ padding: 16px;
52
+ background: #12121a;
53
+ border: 1px solid #2a2a3a;
54
+ border-radius: 8px;
55
+ color: #e0e0e0;
56
+ font-size: 1rem;
57
+ resize: vertical;
58
+ outline: none;
59
+ transition: border-color 0.2s;
60
+ }
61
+
62
+ textarea:focus {
63
+ border-color: #00c896;
64
+ }
65
+
66
+ button {
67
+ align-self: flex-end;
68
+ padding: 12px 32px;
69
+ background: #00c896;
70
+ color: #0a0a0f;
71
+ border: none;
72
+ border-radius: 8px;
73
+ font-size: 1rem;
74
+ font-weight: 600;
75
+ cursor: pointer;
76
+ transition: background 0.2s;
77
+ }
78
+
79
+ button:hover {
80
+ background: #00a87e;
81
+ }
82
+
83
+ button:disabled {
84
+ background: #2a2a3a;
85
+ color: #555;
86
+ cursor: not-allowed;
87
+ }
88
+
89
+ .status-bar {
90
+ display: flex;
91
+ align-items: center;
92
+ gap: 12px;
93
+ margin-top: 24px;
94
+ padding: 14px 18px;
95
+ background: #12121a;
96
+ border: 1px solid #2a2a3a;
97
+ border-radius: 8px;
98
+ font-size: 0.9rem;
99
+ color: #888;
100
+ }
101
+
102
+ .spinner {
103
+ width: 16px;
104
+ height: 16px;
105
+ border: 2px solid #2a2a3a;
106
+ border-top-color: #00c896;
107
+ border-radius: 50%;
108
+ animation: spin 0.8s linear infinite;
109
+ flex-shrink: 0;
110
+ }
111
+
112
+ @keyframes spin {
113
+ to { transform: rotate(360deg); }
114
+ }
115
+
116
+ .response-box {
117
+ margin-top: 32px;
118
+ padding: 28px;
119
+ background: #12121a;
120
+ border: 1px solid #2a2a3a;
121
+ border-radius: 8px;
122
+ }
123
+
124
+ .response-box h2 {
125
+ font-size: 1rem;
126
+ color: #00c896;
127
+ text-transform: uppercase;
128
+ letter-spacing: 1px;
129
+ margin-bottom: 16px;
130
+ }
131
+
132
+ #responseText {
133
+ font-size: 0.97rem;
134
+ line-height: 1.8;
135
+ color: #d0d0d0;
136
+ white-space: pre-wrap;
137
+ }
138
+
139
+ .hidden {
140
+ display: none;
141
+ }
142
+
143
+ footer {
144
+ text-align: center;
145
+ margin-top: 48px;
146
+ font-size: 0.8rem;
147
+ color: #444;
148
+ }
149
+ .disclaimer {
150
+ margin-top: 6px;
151
+ color: #555;
152
+ font-size: 0.75rem;
153
+ }
templates/index.html ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Glitch Squad Biomedical Assistant</title>
7
+ <link rel="stylesheet" href="/static/style.css">
8
+ </head>
9
+ <body>
10
+ <div class="container">
11
+ <header>
12
+ <h1>Glitch Squad</h1>
13
+ <p class="subtitle">Biomedical AI Research Assistant</p>
14
+ </header>
15
+
16
+ <main>
17
+ <div class="search-box">
18
+ <textarea id="queryInput" placeholder="Ask a biomedical research question..." rows="3"></textarea>
19
+ <button id="submitBtn" onclick="submitQuery()">Search Literature</button>
20
+ </div>
21
+
22
+ <div id="statusBar" class="status-bar hidden">
23
+ <span class="spinner"></span>
24
+ <span id="statusText">Retrieving PubMed literature...</span>
25
+ </div>
26
+
27
+ <div id="responseBox" class="response-box hidden">
28
+ <h2>Research Summary</h2>
29
+ <div id="responseText"></div>
30
+ </div>
31
+ </main>
32
+
33
+ <footer>
34
+ <p>Powered by LangGraph + Llama 3.2 + PubMed &mdash; AMD Developer Hackathon 2026</p>
35
+ <p class="disclaimer">This is an AI model and may make mistakes. Always verify responses against primary sources.</p>
36
+ </footer>
37
+ </div>
38
+ <script src="/static/script.js"></script>
39
+ </body>
40
+ </html>