Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENH] FastAPI integration added #31

Merged
merged 11 commits into from
Aug 8, 2024
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ repos:
- id: flake8
language_version: python3
args:
- --extend-ignore=E501
- --extend-ignore=E501,E402
rmanaem marked this conversation as resolved.
Show resolved Hide resolved

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.10.0
Expand Down
150 changes: 118 additions & 32 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,56 +4,142 @@

Currently, the researcher or the data user has to answer a number of queries to get the desired results and it often requires iteration.

The aim of query-tol-ai would be to make this search process more user-friendly by adding an LLM style chatbot interface. This is to be done by utilizing large language models that will be able to interpret the user prompts and initiate the API calls accurately giving the user the desired results.
rmanaem marked this conversation as resolved.
Show resolved Hide resolved
The aim of query-tool-ai would be to make this search process more user-friendly by adding an LLM style chatbot interface. This is to be done by utilizing large language models that will be able to interpret the user prompts and initiate the API calls accurately giving the user the desired results.

## Local installation
- Clone the repository:
```
## Local installation -
### Clone the repository :
```bash
git clone https://github.com/neurobagel/query-tool-ai.git
cd query-tool-ai
```
- Create and activate a virtual environment:
```

Before proceeding with either you need to set the environment variables.
### Set the environment variables -
| Environment Variable | Type | Required | Default Value if Not Set | Example |
| ---------------------- | ------- | ---------------------------------------- | ------------------------ | --------------------------------------------------------- |
| `HOST` | string | No | `0.0.0.0` | `127.0.0.1` |
| `PORT` | integer | No | `8000` | `8080` |


After cloning the repository, you can choose to either use the Dockerized version or run the application locally using Python.
Raya679 marked this conversation as resolved.
Show resolved Hide resolved
#### Follow the instructions for the option that suits you best:

### Option 1 : Docker :
- #### First, [install Docker](https://docs.docker.com/get-docker/).
- #### Build the image using the Dockerfile
After cloning the current repository, build the Docker image locally:
```bash
docker build -t neurobagel-query-tool-ai .
```
- #### Run the container from the built image:
Start the container from the built image. Ensure you map the necessary volumes and set the container name.
```bash
docker run -d \
-p 8000:8000 \
-v ollama:/root/.ollama \
-v /home/user/data:/app/output/ \
--name query-tool-ai-container neurobagel-query-tool-ai
```

Then you can access the query tool ai at http://localhost:8000

**Note:** the query tool ai is listening on port 8000 inside the docker container, replace port 8000 by the port you would like to expose to the host. For example if you'd like to run the tool on port 8080 of your machine you can run the following command:
```bash
docker run -d \
-p 8080:8000 \
-v ollama:/root/.ollama \
-v /home/user/data:/app/output/ \
--name query-tool-ai-container neurobagel-query-tool-ai
```


- #### Verify Host and Port
To ensure the server is running on localhost, you can check the logs or try accessing the server in your browser or with `curl`(assuming the the query tool ai is listening on port 8000):
```bash
curl -X GET "http://localhost:8000/"
```
You should see the response:
```json
{"message": "Welcome to the Neurobagel Query Tool AI API"}
```

### Option 2 : Python
- #### Create and activate a virtual environment:
```bash
python3 -m venv venv
source venv/bin/activate
```
- Install the required packages and pull the Ollama model:
```
- #### Install the required packages and pull the Ollama model:
```bash
pip install -r requirements.txt
ollama pull mistral
```
- Run the python script:

- #### Run the FastAPI Server:
```bash
python app/main.py
```
python3 app/api/url_generator.py
This command starts the FastAPI server on `http://localhost:8000`. (assuming PORT=8000)

- #### Verify Host and Port
To ensure the server is running on localhost, you can check the logs or try accessing the server in your browser or with `curl`:
```bash
curl -X GET "http://localhost:8000/"
```
- You should see the following prompt:
You should see the response:
```json
{"message": "Welcome to the Neurobagel Query Tool AI API"}
```
Enter user query (or 'exit' to quit):

## Interacting with the Query Tool AI
After the local installation is complete, you can ask your query in the following 2 ways.

### API Interaction -
You can interact with the FastAPI application by sending a POST request to the generate_url endpoint. Here’s how you can do it using curl (assuming the the query tool ai is listening on port 8000):
```bash
curl -X POST "http://localhost:8000/generate_url/" -H "Content-Type: application/json" -d '{"query": "your query here"}'
```
Enter your query to get the desired API URL.
Replace "your query here" with the actual query you want to test.

## Dockerized version
- First, [install Docker](https://docs.docker.com/get-docker/).
### Python Script Interaction (Optional) -
- If you have completed the local installation using **`docker`**, write the following command in the terminal.
```bash
docker exec -it query-tool-ai-container python3 / app/api/url_generator.py
```

- After cloning the current repository, build the Docker image locally:
```
docker build -t query-tool-ai .
```
- Run the container from the built image:
```
docker run -d \
-v ollama:/root/.ollama \
-v /home/user/data:/app/output/ \
--name query-tool-ai-container query-tool-ai
```
- Execute the python script:
```
docker exec -it query-tool-ai-container python3 /app/api/url_generator.py
```
- You should see the following prompt:
```
- If you have completed the local installation using **`python`**, write the following command in the terminal.
```bash
python3 app/api/url_generator.py
```

You should see the following prompt -

```bash
Enter user query (or 'exit' to quit):
```

Enter your query to get the desired API URL.


## Testing

Neurobagel API utilizes [Pytest](https://docs.pytest.org/en/7.2.x/) framework for testing.

To run the tests first make sure you're in repository's main directory.

You can then run the tests by executing the following command in your terminal:

```bash
pytest tests
```

### License

Neurobagel API is released under the terms of the [MIT License](LICENSE)







6 changes: 6 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import os
import sys

sys.path.append(
os.path.dirname(os.path.realpath(__file__))
) # Fixes relative import error.
5 changes: 4 additions & 1 deletion app/api/url_generator.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))) # Fixes import errors

sys.path.insert(
0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
) # Fixes import errors

import json
from llm_processing.extractions import extract_information
Expand Down
25 changes: 25 additions & 0 deletions app/main.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import os
from fastapi import FastAPI
from dotenv import load_dotenv
from router import routes

# Load environment variables from .env file
load_dotenv()

app = FastAPI()

app.include_router(routes.router)


@app.get("/")
async def root():
return {"message": "Welcome to the Neurobagel Query Tool AI API"}


if __name__ == "__main__":
import uvicorn

host = os.getenv("HOST", "0.0.0.0")
Raya679 marked this conversation as resolved.
Show resolved Hide resolved
port = int(os.getenv("PORT", 8000))

uvicorn.run(app, host=host, port=port)
Empty file added app/router/__init__.py
Empty file.
26 changes: 26 additions & 0 deletions app/router/routes.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import sys
import os

sys.path.insert(
0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
) # Fixes import errors

from fastapi import APIRouter, HTTPException, status
from pydantic import BaseModel
from api.url_generator import get_api_url


router = APIRouter()


class QueryRequest(BaseModel):
query: str


@router.post("/generate_url/")
async def generate_url(request: QueryRequest):
try:
api_url = get_api_url(request.query)
return {"response": api_url}
except Exception as e:
raise HTTPException(status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e))
11 changes: 9 additions & 2 deletions entrypoint.sh
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
#!/bin/bash

ollama serve &&
ollama run mistral.
# Start the Ollama services first
ollama serve &
ollama run mistral &

# Start the FastAPI application
uvicorn main:app --host 0.0.0.0 --port 8000

# Wait for all background processes to complete
wait
4 changes: 4 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ attrs==23.2.0
certifi==2024.6.2
cfgv==3.4.0
charset-normalizer==3.3.2
click==8.1.7
dataclasses-json==0.6.7
distlib==0.3.8
exceptiongroup==1.2.1
fastapi==0.112.0
filelock==3.14.0
frozenlist==1.4.1
fsspec==2024.6.0
Expand Down Expand Up @@ -48,12 +50,14 @@ PyYAML==6.0.1
requests==2.32.3
sniffio==1.3.1
SQLAlchemy==2.0.30
starlette==0.37.2
tenacity==8.3.0
tomli==2.0.1
tqdm==4.66.4
types-requests==2.32.0.20240622
typing-inspect==0.9.0
typing_extensions==4.12.2
urllib3==2.2.1
uvicorn==0.30.5
virtualenv==20.26.2
yarl==1.9.4
67 changes: 67 additions & 0 deletions tests/test_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import pytest
from fastapi.testclient import TestClient
from app.main import app
from unittest.mock import patch

client = TestClient(app)


def test_root():
response = client.get("/")
assert response.status_code == 200
assert response.json() == {
"message": "Welcome to the Neurobagel Query Tool AI API"
}


@pytest.mark.parametrize(
"query, expected_status_code, expected_response",
[
(
"How many subjects between 20 and 80 yrs old who have at least 3 phenotypic sessions and 2 imaging sessions?",
200,
{
"response": "https://api.neurobagel.org/query/?min_age=20.0&max_age=80.0&min_num_imaging_sessions=2&min_num_phenotypic_sessions=3"
},
),
(
"male healthy control subjects",
200,
{
"response": "https://api.neurobagel.org/query/?sex=snomed:248153007&is_control=true"
},
),
(
"subjects diagnosed with traumatic brain injury assessed with balloon analogue risk task and T2 weighted image modality",
200,
{
"response": "https://api.neurobagel.org/query/?diagnosis=snomed:127295002&is_control=false&assessment=cogatlas:trm_4d559bcd67c18&image_modal=nidm:T2Weighted"
},
),
(
"female healthy control subjects with parkinsons",
200,
{
"response": "Subjects cannot both be healthy controls and have a diagnosis."
},
),
(
"Female subjects suffering from Arachnoiditis assessed by regulated heat stimulation",
200,
{
"response": "Unfortunately, Neurobagel does not yet support searches for the following terms: arachnoiditis diagnosis, regulated heat stimulation assessment"
},
),
],
)
@patch(
"app.router.routes.get_api_url",
)
def test_generate_url(
mock_get_api_url, query, expected_status_code, expected_response
):
mock_get_api_url.return_value = expected_response
response = client.post("/generate_url/", json={"query": query})

assert response.status_code == expected_status_code
assert response.json() == expected_response