David Tang
commited on
Commit
Β·
fcae81e
0
Parent(s):
Initial commit: Hugging Face Spaces app setup
Browse files- .gitattributes +35 -0
- .gitignore +52 -0
- README.md +102 -0
- app.py +154 -0
- assets/brain.svg +1 -0
- assets/custom.css +131 -0
- assets/heartbreak.svg +1 -0
- assets/injection.svg +1 -0
- assets/medical_cross_icon_144218.ico +0 -0
- assets/sickface.svg +1 -0
- docs.py +127 -0
- requirements.txt +4 -0
.gitattributes
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
*.7z filter=lfs diff=lfs merge=lfs -text
|
| 2 |
+
*.arrow filter=lfs diff=lfs merge=lfs -text
|
| 3 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 4 |
+
*.bz2 filter=lfs diff=lfs merge=lfs -text
|
| 5 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 6 |
+
*.ftz filter=lfs diff=lfs merge=lfs -text
|
| 7 |
+
*.gz filter=lfs diff=lfs merge=lfs -text
|
| 8 |
+
*.h5 filter=lfs diff=lfs merge=lfs -text
|
| 9 |
+
*.joblib filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.lfs.* filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.mlmodel filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.model filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.npy filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.ot filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.parquet filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.pb filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.pickle filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.pkl filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 23 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 24 |
+
*.rar filter=lfs diff=lfs merge=lfs -text
|
| 25 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 26 |
+
saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
| 27 |
+
*.tar.* filter=lfs diff=lfs merge=lfs -text
|
| 28 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
*.tflite filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
*.tgz filter=lfs diff=lfs merge=lfs -text
|
| 31 |
+
*.wasm filter=lfs diff=lfs merge=lfs -text
|
| 32 |
+
*.xz filter=lfs diff=lfs merge=lfs -text
|
| 33 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 34 |
+
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
+
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
.gitignore
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
pyproject.toml
|
| 2 |
+
poetry.lock
|
| 3 |
+
.venv
|
| 4 |
+
.env
|
| 5 |
+
.DS_Store
|
| 6 |
+
__pycache__
|
| 7 |
+
.pytest_cache
|
| 8 |
+
.ruff_cache
|
| 9 |
+
.vscode
|
| 10 |
+
.idea
|
| 11 |
+
.cursorrules
|
| 12 |
+
uv.lock
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
# Python
|
| 16 |
+
*.py[cod]
|
| 17 |
+
*$py.class
|
| 18 |
+
*.so
|
| 19 |
+
.Python
|
| 20 |
+
env/
|
| 21 |
+
build/
|
| 22 |
+
develop-eggs/
|
| 23 |
+
dist/
|
| 24 |
+
downloads/
|
| 25 |
+
eggs/
|
| 26 |
+
.eggs/
|
| 27 |
+
lib/
|
| 28 |
+
lib64/
|
| 29 |
+
parts/
|
| 30 |
+
sdist/
|
| 31 |
+
var/
|
| 32 |
+
wheels/
|
| 33 |
+
*.egg-info/
|
| 34 |
+
.installed.cfg
|
| 35 |
+
*.egg
|
| 36 |
+
|
| 37 |
+
# Virtual Environment
|
| 38 |
+
venv/
|
| 39 |
+
ENV/
|
| 40 |
+
|
| 41 |
+
# IDE
|
| 42 |
+
*.swp
|
| 43 |
+
*.swo
|
| 44 |
+
.project
|
| 45 |
+
.pydevproject
|
| 46 |
+
.settings/
|
| 47 |
+
|
| 48 |
+
# Logs
|
| 49 |
+
*.log
|
| 50 |
+
|
| 51 |
+
# OS
|
| 52 |
+
Thumbs.db
|
README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
title: Agentic Coach Advisor Medgemma
|
| 3 |
+
emoji: π¬
|
| 4 |
+
colorFrom: yellow
|
| 5 |
+
colorTo: purple
|
| 6 |
+
sdk: gradio
|
| 7 |
+
sdk_version: 5.0.1
|
| 8 |
+
app_file: app.py
|
| 9 |
+
pinned: false
|
| 10 |
+
license: mit
|
| 11 |
+
short_description: Using medGemma as an agent for giving basic health coaching
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
# MedGemma Agent: AI-Powered Medical Assistant
|
| 17 |
+
|
| 18 |
+

|
| 19 |
+
|
| 20 |
+
## π₯ Overview
|
| 21 |
+
|
| 22 |
+
MedGemma Agent is an advanced AI-powered medical assistant that provides accessible and accurate medical information to patients and non-medical professionals. Built on top of Google's MedGemma model, this application combines state-of-the-art medical language understanding with multimodal capabilities to deliver clear, concise, and reliable medical insights.
|
| 23 |
+
|
| 24 |
+
## β¨ Key Features
|
| 25 |
+
|
| 26 |
+
- **Multimodal Understanding**: Process both text queries and medical images
|
| 27 |
+
- **Real-time Responses**: Stream responses for an interactive experience
|
| 28 |
+
- **Wikipedia Integration**: Access to verified medical information
|
| 29 |
+
- **User-friendly Interface**: Clean, modern UI with example queries
|
| 30 |
+
- **Secure API**: Protected endpoints with API key authentication
|
| 31 |
+
|
| 32 |
+
## π Technical Implementation
|
| 33 |
+
|
| 34 |
+
### Backend Architecture
|
| 35 |
+
|
| 36 |
+
The application is built using:
|
| 37 |
+
- **Modal**: For serverless deployment and GPU acceleration
|
| 38 |
+
- **FastAPI**: For robust API endpoints
|
| 39 |
+
- **VLLM**: For efficient model inference
|
| 40 |
+
- **MedGemma-4B**: Fine-tuned medical language model
|
| 41 |
+
- **Wikipedia API**: For additional medical context
|
| 42 |
+
|
| 43 |
+
### Key Components
|
| 44 |
+
|
| 45 |
+
1. **Model Deployment**
|
| 46 |
+
- Utilizes Modal's GPU-accelerated containers
|
| 47 |
+
- Implements efficient model loading with VLLM
|
| 48 |
+
- Supports bfloat16 precision for optimal performance
|
| 49 |
+
|
| 50 |
+
2. **API Layer**
|
| 51 |
+
- Streaming responses for real-time interaction
|
| 52 |
+
- Secure API key authentication
|
| 53 |
+
- Base64 image processing for multimodal inputs
|
| 54 |
+
|
| 55 |
+
3. **Frontend Interface**
|
| 56 |
+
- Built with Gradio for seamless user interaction
|
| 57 |
+
- Custom CSS theming for professional appearance
|
| 58 |
+
- Example queries for common medical scenarios
|
| 59 |
+
|
| 60 |
+
## π οΈ Usage
|
| 61 |
+
|
| 62 |
+
1. **Text Queries**
|
| 63 |
+
- Ask medical questions in natural language
|
| 64 |
+
- Get clear, patient-friendly explanations
|
| 65 |
+
- Example: "What are the symptoms of a stroke?"
|
| 66 |
+
|
| 67 |
+
2. **Image Analysis**
|
| 68 |
+
- Upload medical images for analysis
|
| 69 |
+
- Get AI-powered insights about the image
|
| 70 |
+
- Supports common medical image formats
|
| 71 |
+
|
| 72 |
+
## π Security
|
| 73 |
+
|
| 74 |
+
- API key authentication for all requests
|
| 75 |
+
- Secure image processing
|
| 76 |
+
- Protected model endpoints
|
| 77 |
+
|
| 78 |
+
## ποΈ Technical Stack
|
| 79 |
+
|
| 80 |
+
- **Backend**: Modal, FastAPI, VLLM
|
| 81 |
+
- **Frontend**: Gradio
|
| 82 |
+
- **Model**: MedGemma-4B (unsloth/medgemma-4b-it-unsloth-bnb-4bit)
|
| 83 |
+
- **Additional Tools**: Wikipedia API for medical context
|
| 84 |
+
|
| 85 |
+
## π― Performance
|
| 86 |
+
|
| 87 |
+
- Optimized for low latency responses
|
| 88 |
+
- GPU-accelerated inference
|
| 89 |
+
- Efficient memory utilization with 4-bit quantization
|
| 90 |
+
- Maximum context length of 8192 tokens
|
| 91 |
+
|
| 92 |
+
## π€ Contributing
|
| 93 |
+
|
| 94 |
+
We welcome contributions! Please feel free to submit issues and pull requests.
|
| 95 |
+
|
| 96 |
+
## π License
|
| 97 |
+
|
| 98 |
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
| 99 |
+
|
| 100 |
+
---
|
| 101 |
+
|
| 102 |
+
Built with β€οΈ for the Hugging Face Spaces Hackathon.
|
app.py
ADDED
|
@@ -0,0 +1,154 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import httpx
|
| 3 |
+
import os
|
| 4 |
+
import json
|
| 5 |
+
import base64
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import io
|
| 8 |
+
import docs
|
| 9 |
+
|
| 10 |
+
API_KEY = os.getenv("API_KEY")
|
| 11 |
+
MODAL_API_ENDPOINT = os.getenv("MODAL_API_ENDPOINT")
|
| 12 |
+
|
| 13 |
+
def encode_image_to_base64(image):
|
| 14 |
+
if image is None:
|
| 15 |
+
return None
|
| 16 |
+
buffered = io.BytesIO()
|
| 17 |
+
image.save(buffered, format="PNG")
|
| 18 |
+
return base64.b64encode(buffered.getvalue()).decode()
|
| 19 |
+
|
| 20 |
+
async def call_my_api(message, history, image=None):
|
| 21 |
+
# Support multimodal: message can be dict with 'text' and 'files'
|
| 22 |
+
user_text = message["text"] if isinstance(message, dict) else message
|
| 23 |
+
if user_text.strip().lower().startswith("(example)"):
|
| 24 |
+
user_text = user_text.strip()[9:].lstrip()
|
| 25 |
+
image_obj = None
|
| 26 |
+
|
| 27 |
+
if image is None and isinstance(message, dict) and message.get("files"):
|
| 28 |
+
# message["files"] is a list of file paths or file objects
|
| 29 |
+
# For Gradio, it may be a list of PIL Images
|
| 30 |
+
image_obj = message["files"][0] if message["files"] else None
|
| 31 |
+
else:
|
| 32 |
+
image_obj = image
|
| 33 |
+
image_base64 = encode_image_to_base64(image_obj) if image_obj else None
|
| 34 |
+
|
| 35 |
+
payload = {
|
| 36 |
+
"prompt": "You are a helpful and positive health coach. Explain in simple terms to a patient or non-medical person on the following question or statement.\n\n" + user_text,
|
| 37 |
+
"image": image_base64
|
| 38 |
+
}
|
| 39 |
+
|
| 40 |
+
headers = {
|
| 41 |
+
"Content-Type": "application/json",
|
| 42 |
+
"X-API-Key": API_KEY
|
| 43 |
+
}
|
| 44 |
+
|
| 45 |
+
try:
|
| 46 |
+
async with httpx.AsyncClient() as client:
|
| 47 |
+
async with client.stream('POST', MODAL_API_ENDPOINT, json=payload, headers=headers, timeout=120.0) as response:
|
| 48 |
+
response.raise_for_status()
|
| 49 |
+
async for line in response.aiter_lines():
|
| 50 |
+
if line.startswith('data: '):
|
| 51 |
+
try:
|
| 52 |
+
data = json.loads(line[6:]) # Remove 'data: ' prefix
|
| 53 |
+
if data['type'] == 'final':
|
| 54 |
+
yield data['content']['response']
|
| 55 |
+
elif data['type'] == 'thinking':
|
| 56 |
+
yield data['content'].get('message', '')
|
| 57 |
+
elif data['type'] == 'tool_call':
|
| 58 |
+
yield f"Using tool: {data['content'].get('name', '')}"
|
| 59 |
+
elif data['type'] == 'tool_result':
|
| 60 |
+
yield f"Tool result: {data['content'].get('result', '')}"
|
| 61 |
+
except json.JSONDecodeError:
|
| 62 |
+
continue
|
| 63 |
+
except httpx.RequestError as e:
|
| 64 |
+
print(f"Error calling API: {e}")
|
| 65 |
+
yield f"Error: Could not connect to the API. {e}"
|
| 66 |
+
except Exception as e:
|
| 67 |
+
print(f"An unexpected error occurred: {e}")
|
| 68 |
+
yield "An unexpected error occurred."
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
def vote(chatbot, history, vote):
|
| 72 |
+
print(f"Vote: {vote}")
|
| 73 |
+
print(f"Chatbot: {chatbot}")
|
| 74 |
+
print(f"History: {history}")
|
| 75 |
+
return chatbot, history
|
| 76 |
+
|
| 77 |
+
with gr.Blocks(
|
| 78 |
+
theme=gr.themes.Soft(
|
| 79 |
+
primary_hue="blue",
|
| 80 |
+
secondary_hue="blue",
|
| 81 |
+
neutral_hue="slate",
|
| 82 |
+
font=["Inter", "sans-serif"],
|
| 83 |
+
),
|
| 84 |
+
css_paths=["assets/custom.css"],
|
| 85 |
+
title="Agent medGemma"
|
| 86 |
+
) as demo:
|
| 87 |
+
chatbot = gr.Chatbot(
|
| 88 |
+
placeholder="Ask me anything about a medical condition. \n\nYou can also upload an medical image to get more information.",
|
| 89 |
+
type="messages",
|
| 90 |
+
height=600
|
| 91 |
+
)
|
| 92 |
+
chatbot.like(vote, inputs=[chatbot, gr.State([]), gr.State("")], outputs=[chatbot, gr.State([])])
|
| 93 |
+
|
| 94 |
+
with gr.Row():
|
| 95 |
+
with gr.Column(scale=2):
|
| 96 |
+
gr.Markdown("# π₯ Agent MedGemma", elem_id="main-title")
|
| 97 |
+
with gr.Column(scale=3):
|
| 98 |
+
gr.Markdown(
|
| 99 |
+
"<div class='tagline'>Simple and accessible medical facts</div>",
|
| 100 |
+
elem_id="main-tagline"
|
| 101 |
+
)
|
| 102 |
+
|
| 103 |
+
with gr.Row():
|
| 104 |
+
with gr.Column(scale=1, elem_id="chat-col"):
|
| 105 |
+
chat_interface = gr.ChatInterface(
|
| 106 |
+
multimodal=True,
|
| 107 |
+
fn=call_my_api,
|
| 108 |
+
chatbot=chatbot,
|
| 109 |
+
theme="soft",
|
| 110 |
+
examples=[
|
| 111 |
+
"(example) Tell me about the causes of a heart attack.",
|
| 112 |
+
"(example) What should I do with serious vomiting?",
|
| 113 |
+
"(example) Should I take double my insulin now that I forgot to take it?",
|
| 114 |
+
"(example) What are the most common symptoms of a stroke?"
|
| 115 |
+
],
|
| 116 |
+
example_icons=["assets/heartbreak.svg",
|
| 117 |
+
"assets/sickface.svg",
|
| 118 |
+
"assets/injection.svg",
|
| 119 |
+
"assets/brain.svg"]
|
| 120 |
+
)
|
| 121 |
+
|
| 122 |
+
gr.Markdown(
|
| 123 |
+
"Ask me anything about a medical condition.<br><br>You can also upload an medical image to get more information.",
|
| 124 |
+
elem_id="main-instructions"
|
| 125 |
+
)
|
| 126 |
+
|
| 127 |
+
gr.Markdown(
|
| 128 |
+
"""
|
| 129 |
+
<div class='disclaimer-box'>
|
| 130 |
+
<p>
|
| 131 |
+
<strong>Medical Disclaimer:</strong> This AI assistant is designed for educational and informational purposes only.
|
| 132 |
+
It does not constitute medical advice, diagnosis, or treatment. Always consult with qualified healthcare professionals
|
| 133 |
+
for medical decisions. This tool aims to promote health literacy and empower individuals to better understand their
|
| 134 |
+
health, but should not replace professional medical consultation.
|
| 135 |
+
</p>
|
| 136 |
+
</div>
|
| 137 |
+
""",
|
| 138 |
+
elem_id="disclaimer"
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
gr.Markdown(
|
| 142 |
+
"""
|
| 143 |
+
<div style='text-align: center; margin-top: 20px; padding: 10px; border-top: 1px solid #e0e0e0;'>
|
| 144 |
+
<a href='/docs' style='text-decoration: none; color: #666;'>π View Technical Documentation</a>
|
| 145 |
+
</div>
|
| 146 |
+
""",
|
| 147 |
+
elem_id="footer"
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
with demo.route("Technical Documentation", "/docs"):
|
| 151 |
+
docs.docs_demo.render()
|
| 152 |
+
|
| 153 |
+
if __name__ == "__main__":
|
| 154 |
+
demo.launch(favicon_path="assets/medical_cross_icon_144218.ico")
|
assets/brain.svg
ADDED
|
|
assets/custom.css
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/* Custom Gradio Styles */
|
| 2 |
+
.gradio-container {
|
| 3 |
+
margin: auto;
|
| 4 |
+
padding: 20px;
|
| 5 |
+
}
|
| 6 |
+
.gradio-markdown {
|
| 7 |
+
padding: 20px;
|
| 8 |
+
}
|
| 9 |
+
#main-title {
|
| 10 |
+
display: flex;
|
| 11 |
+
align-items: center;
|
| 12 |
+
height: 100%;
|
| 13 |
+
}
|
| 14 |
+
#main-tagline {
|
| 15 |
+
display: flex;
|
| 16 |
+
align-items: center;
|
| 17 |
+
height: 100%;
|
| 18 |
+
justify-content: flex-end;
|
| 19 |
+
}
|
| 20 |
+
.tagline {
|
| 21 |
+
color: #4b5563;
|
| 22 |
+
font-size: 1.2em;
|
| 23 |
+
margin-bottom: 0;
|
| 24 |
+
text-align: right;
|
| 25 |
+
}
|
| 26 |
+
.gradio-markdown h1 {
|
| 27 |
+
color: #2563eb;
|
| 28 |
+
font-size: 2.5em;
|
| 29 |
+
margin-bottom: 10px;
|
| 30 |
+
text-align: left;
|
| 31 |
+
}
|
| 32 |
+
.gradio-chatbot, .gradio-image, .gradio-interface, .gradio-column {
|
| 33 |
+
border: 1.5px solid #cbd5e1;
|
| 34 |
+
border-radius: 10px;
|
| 35 |
+
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.07);
|
| 36 |
+
background: #f8fafc;
|
| 37 |
+
padding: 12px;
|
| 38 |
+
min-height: 600px;
|
| 39 |
+
height: 600px;
|
| 40 |
+
box-sizing: border-box;
|
| 41 |
+
}
|
| 42 |
+
.gradio-button {
|
| 43 |
+
border-radius: 8px;
|
| 44 |
+
transition: all 0.3s ease;
|
| 45 |
+
}
|
| 46 |
+
.gradio-button:hover {
|
| 47 |
+
transform: translateY(-2px);
|
| 48 |
+
box-shadow: 0 4px 6px -1px rgb(0 0 0 / 0.1);
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
/* DARK MODE OVERRIDES */
|
| 52 |
+
@media (prefers-color-scheme: dark) {
|
| 53 |
+
body, .gradio-container {
|
| 54 |
+
background: #181a20 !important;
|
| 55 |
+
}
|
| 56 |
+
#main-title h1 {
|
| 57 |
+
color: #60a5fa;
|
| 58 |
+
}
|
| 59 |
+
.tagline {
|
| 60 |
+
color: #cbd5e1;
|
| 61 |
+
}
|
| 62 |
+
.gradio-chatbot, .gradio-image, .gradio-interface, .gradio-column {
|
| 63 |
+
background: #23262f;
|
| 64 |
+
border: 1.5px solid #334155;
|
| 65 |
+
min-height: 600px;
|
| 66 |
+
height: 600px;
|
| 67 |
+
box-sizing: border-box;
|
| 68 |
+
}
|
| 69 |
+
}
|
| 70 |
+
|
| 71 |
+
#chat-col, #img-col {
|
| 72 |
+
border: 2px solid #cbd5e1 !important;
|
| 73 |
+
border-radius: 10px !important;
|
| 74 |
+
background: #f8fafc !important;
|
| 75 |
+
box-sizing: border-box;
|
| 76 |
+
padding: 12px;
|
| 77 |
+
}
|
| 78 |
+
@media (prefers-color-scheme: dark) {
|
| 79 |
+
#chat-col, #img-col {
|
| 80 |
+
border: 2px solid #60a5fa !important;
|
| 81 |
+
background: #23262f !important;
|
| 82 |
+
}
|
| 83 |
+
}
|
| 84 |
+
|
| 85 |
+
.disclaimer-box {
|
| 86 |
+
background: rgba(30, 41, 59, 0.7); /* semi-transparent dark slate */
|
| 87 |
+
border-left: 4px solid #2563eb; /* blue-600 */
|
| 88 |
+
padding: 15px;
|
| 89 |
+
margin: 20px 0;
|
| 90 |
+
border-radius: 4px;
|
| 91 |
+
color: var(--body-text-color, #f1f5f9); /* fallback to light text */
|
| 92 |
+
font-size: 0.95em;
|
| 93 |
+
}
|
| 94 |
+
body.light .disclaimer-box {
|
| 95 |
+
background: rgba(248, 249, 250, 0.85); /* light background for light mode */
|
| 96 |
+
color: #495057;
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
#footer a {
|
| 100 |
+
color: #a1a1aa !important;
|
| 101 |
+
font-size: 1em;
|
| 102 |
+
text-decoration: none;
|
| 103 |
+
transition: color 0.2s;
|
| 104 |
+
}
|
| 105 |
+
#footer a:hover {
|
| 106 |
+
color: #2563eb !important;
|
| 107 |
+
text-decoration: underline;
|
| 108 |
+
}
|
| 109 |
+
|
| 110 |
+
#main-instructions {
|
| 111 |
+
text-align: center;
|
| 112 |
+
margin-top: 2em;
|
| 113 |
+
margin-bottom: 2em;
|
| 114 |
+
font-size: 1.12em;
|
| 115 |
+
color: #a1a1aa; /* subtle gray for less prominence */
|
| 116 |
+
font-weight: 400;
|
| 117 |
+
letter-spacing: 0.01em;
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
#docs-demo {
|
| 121 |
+
max-width: 800px;
|
| 122 |
+
margin: 2em auto;
|
| 123 |
+
background: rgba(30,41,59,0.7);
|
| 124 |
+
border-radius: 8px;
|
| 125 |
+
padding: 2em;
|
| 126 |
+
color: var(--body-text-color, #f1f5f9);
|
| 127 |
+
}
|
| 128 |
+
body.light #docs-demo {
|
| 129 |
+
background: rgba(248,249,250,0.95);
|
| 130 |
+
color: #23262f;
|
| 131 |
+
}
|
assets/heartbreak.svg
ADDED
|
|
assets/injection.svg
ADDED
|
|
assets/medical_cross_icon_144218.ico
ADDED
|
|
assets/sickface.svg
ADDED
|
|
docs.py
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import gradio as gr
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
with gr.Blocks(title="Technical Documentation", css="footer {visibility: hidden}") as docs_demo:
|
| 6 |
+
|
| 7 |
+
with gr.Column():
|
| 8 |
+
gr.Markdown("""
|
| 9 |
+
# Technical Documentation
|
| 10 |
+
|
| 11 |
+
## Overview
|
| 12 |
+
This page provides details about the architecture, API, and usage of the MedGemma Agent application.
|
| 13 |
+
|
| 14 |
+
## Features
|
| 15 |
+
- Multimodal (text + image)
|
| 16 |
+
- Wikipedia tool integration
|
| 17 |
+
- Real-time streaming
|
| 18 |
+
- Medical knowledge base
|
| 19 |
+
|
| 20 |
+
---
|
| 21 |
+
|
| 22 |
+
## Architecture
|
| 23 |
+
- **Frontend:** Gradio Blocks, custom CSS
|
| 24 |
+
- **Backend:** Modal, FastAPI, VLLM, MedGemma-4B
|
| 25 |
+
- **Security:** API key authentication
|
| 26 |
+
|
| 27 |
+
### ποΈ Technical Stack
|
| 28 |
+
- Streaming responses for real-time interaction
|
| 29 |
+
- Secure API key authentication
|
| 30 |
+
- Base64 image processing for multimodal inputs
|
| 31 |
+
|
| 32 |
+
### Frontend Interface
|
| 33 |
+
- Built with Gradio for seamless user interaction
|
| 34 |
+
- Custom CSS theming for professional appearance
|
| 35 |
+
- Example queries for common medical scenarios
|
| 36 |
+
|
| 37 |
+
```mermaid
|
| 38 |
+
graph TD
|
| 39 |
+
A[MedGemma Agent] --> B[Backend]
|
| 40 |
+
A --> C[Frontend]
|
| 41 |
+
A --> D[Model]
|
| 42 |
+
|
| 43 |
+
B --> B1[Modal]
|
| 44 |
+
B --> B2[FastAPI]
|
| 45 |
+
B --> B3[VLLM]
|
| 46 |
+
|
| 47 |
+
C --> C1[Gradio]
|
| 48 |
+
C --> C2[Custom CSS]
|
| 49 |
+
|
| 50 |
+
D --> D1[MedGemma-4B]
|
| 51 |
+
D --> D2[4-bit Quantization]
|
| 52 |
+
```
|
| 53 |
+
""")
|
| 54 |
+
|
| 55 |
+
gr.Markdown("""
|
| 56 |
+
## Backend Architecture
|
| 57 |
+
|
| 58 |
+
### π― Performance Features
|
| 59 |
+
|
| 60 |
+
- Optimized for low latency responses
|
| 61 |
+
- GPU-accelerated inference
|
| 62 |
+
- Efficient memory utilization with 4-bit quantization
|
| 63 |
+
- Maximum context length of 8192 tokens
|
| 64 |
+
|
| 65 |
+
### π Security Measures
|
| 66 |
+
|
| 67 |
+
- API key authentication for all requests
|
| 68 |
+
- Secure image processing
|
| 69 |
+
- Protected model endpoints
|
| 70 |
+
|
| 71 |
+
```mermaid
|
| 72 |
+
flowchart LR
|
| 73 |
+
A[Client] --> B[FastAPI]
|
| 74 |
+
B --> C[Modal Container]
|
| 75 |
+
C --> D[VLLM]
|
| 76 |
+
D --> E[MedGemma-4B]
|
| 77 |
+
B --> F[Wikipedia API]
|
| 78 |
+
```
|
| 79 |
+
""")
|
| 80 |
+
with gr.Row():
|
| 81 |
+
with gr.Column():
|
| 82 |
+
gr.Markdown("""
|
| 83 |
+
## πΎ Model Deployment
|
| 84 |
+
|
| 85 |
+
### Model
|
| 86 |
+
- **Model:** unsloth/medgemma-4b-it-unsloth-bnb-4bit
|
| 87 |
+
- **Context Length:** 8192 tokens
|
| 88 |
+
- **Quantization:** 4-bit, bfloat16
|
| 89 |
+
- Utilizes Modal's GPU-accelerated containers
|
| 90 |
+
- Implements efficient model loading with VLLM
|
| 91 |
+
- Supports bfloat16 precision for optimal performance
|
| 92 |
+
""")
|
| 93 |
+
with gr.Column():
|
| 94 |
+
gr.Markdown("""
|
| 95 |
+
```mermaid
|
| 96 |
+
graph TD
|
| 97 |
+
A[Model Loading] --> B[GPU Acceleration]
|
| 98 |
+
B --> C[4-bit Quantization]
|
| 99 |
+
C --> D[8192 Token Context]
|
| 100 |
+
D --> E[Streaming Response]
|
| 101 |
+
```
|
| 102 |
+
""")
|
| 103 |
+
with gr.Column():
|
| 104 |
+
gr.Markdown("""
|
| 105 |
+
## π System Architecture
|
| 106 |
+
|
| 107 |
+
```mermaid
|
| 108 |
+
flowchart TD
|
| 109 |
+
A[User Interface] --> B[API Gateway]
|
| 110 |
+
B --> C[Authentication]
|
| 111 |
+
C --> D[Model Service]
|
| 112 |
+
D --> E[Wikipedia Service]
|
| 113 |
+
D --> F[Image Processing]
|
| 114 |
+
F --> G[Model Inference]
|
| 115 |
+
E --> H[Response Generation]
|
| 116 |
+
G --> H
|
| 117 |
+
H --> I[Stream Response]
|
| 118 |
+
I --> A
|
| 119 |
+
```
|
| 120 |
+
""")
|
| 121 |
+
|
| 122 |
+
gr.Markdown("""
|
| 123 |
+
[Back to Main Application](https://huggingface.co/spaces/Agents-MCP-Hackathon/agentic-coach-advisor-medgemma)
|
| 124 |
+
""")
|
| 125 |
+
|
| 126 |
+
if __name__ == "__main__":
|
| 127 |
+
docs_demo.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio>=4.0.0
|
| 2 |
+
httpx>=0.24.0
|
| 3 |
+
Pillow>=10.0.0
|
| 4 |
+
python-multipart>=0.0.6
|