Skip to content

Commit e93a6b8

Browse files
Merge pull request #1 from bartosz-grabowski/feature/aws-cloud-deployment
feat: add optional AWS deployment with Terraform
2 parents 3583bb4 + e9c6f50 commit e93a6b8

20 files changed

Lines changed: 1151 additions & 95 deletions

.gitignore

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,12 @@ wheels/
1212
# Directories
1313
ollama/ollama/
1414
db/*
15-
!db/.keep
15+
!db/.keep
16+
!db/*.example
17+
18+
# Terraform-generated files
19+
.terraform/
20+
*.tfstate
21+
*.tfstate.*
22+
*.tfvars
23+
*.tfvars.json

README.md

Lines changed: 150 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,200 @@
1-
# 🧠 SQLAgent
1+
# SQLAgent
22

3-
A lightweight open-source **LLM agent with RAG (Retrieval-Augmented Generation)** capabilities, able to query both its internal model knowledge and a connected **MySQL database**. It allows users to ask natural-language questions that the agent translates into SQL queries and executes automatically.
3+
SQLAgent is a lightweight learning project that answers natural-language questions about a MySQL database by using an LLM to inspect schema, generate SQL, execute the query, and summarize the result.
44

5-
## ⚠️ Disclaimer
5+
The project now supports two deployment modes:
66

7-
This project is intended **for learning purposes only**.
8-
It is **not designed or guaranteed for production use**. Any deployment or usage is done **at your own risk**.
9-
The author assumes **no responsibility** for data loss, security issues, or system failures resulting from the use of this project.
7+
- Local Docker deployment with a locally hosted Ollama model.
8+
- Optional AWS deployment with Terraform, where Ollama runs on an EC2 instance.
109

11-
## 🚀 Features
12-
- 💡 **RAG-powered reasoning** — Combines generative LLM reasoning with real database retrieval.
13-
- 🗄️ **MySQL integration** — Directly connects to your MySQL data.
14-
- 🐳 **Dockerized setup** — One-command deployment with Docker Compose.
15-
- 🌐 **HTTP API** — Simple API endpoint for natural-language queries.
10+
## Disclaimer
1611

17-
## 🧱 Requirements
12+
This project is intended for learning purposes only.
13+
It is not designed or guaranteed for production use.
14+
Any deployment or usage is done at your own risk.
1815

19-
- 🐳 Docker
20-
- 💻 Basic terminal usage
16+
## Features
2117

22-
## 🛠️ Setup Instructions
18+
- Natural-language to SQL querying against a MySQL database.
19+
- Docker-based local development workflow.
20+
- Configurable Ollama endpoint, so the app can target either local or cloud-hosted Ollama.
21+
- Optional AWS infrastructure managed with Terraform.
22+
- Health endpoint at `GET /health` for deployment checks.
23+
24+
## Requirements
25+
26+
### Local mode
27+
28+
- Docker
29+
- Docker Compose
30+
31+
### AWS mode
32+
33+
- Terraform 1.6+
34+
- An AWS account
35+
- AWS CLI credentials configured locally
36+
- A Git-accessible copy of this repository
37+
38+
## Local Deployment
2339

2440
### 1. Add your database dump
25-
Place your SQL dump file in `./db/db.sql`.
2641

27-
This file will be imported into the MySQL container during startup.
42+
For a quick test, copy the included example dump:
2843

29-
### 2. Set the MySQL password
44+
```bash
45+
cp db/db.sql.example db/db.sql
46+
```
3047

31-
Store your MySQL root password in `./db/passwd.txt`. This value will be read by the container during startup.
48+
If you want to use your own data instead, place your SQL dump file at `./db/db.sql`.
49+
50+
### 2. Set the MySQL password
3251

33-
Make sure that `./db/passwd.txt` contains *only* the password and **no trailing newline**. Many editors automatically append a newline at the end of the file, which will cause MySQL authentication to fail.
52+
Store the MySQL root password in `./db/passwd.txt`.
3453

35-
To safely create the file without adding a newline, run:
54+
Make sure the file contains only the password and no trailing newline:
3655

3756
```bash
3857
echo -n "your_password_here" > db/passwd.txt
3958
```
4059

60+
### 3. Optionally choose a local model
4161

42-
### 3. (Optional) Choose the model
43-
44-
If you wish to change the default Ollama model (`gpt-oss:20b`), set the `OLLAMA_MODEL` environment variable:
62+
The default local model is `gpt-oss:20b`.
4563

4664
```bash
4765
export OLLAMA_MODEL=model_identifier
4866
```
4967

50-
### 4. Start the agent
51-
52-
Run the following command from the project root:
68+
### 4. Start the local stack
5369

5470
```bash
5571
docker compose up
5672
```
5773

58-
This will start both the LLM agent and the MySQL database, automatically initializing the schema and data from `./db/db.sql`, and reading the MySQL password from `./db/passwd.txt`.
74+
This starts:
5975

60-
> ⚠️ **Note:** If this is your first run or the required Ollama models are not yet downloaded, please wait until the model download completes before sending any queries to the agent.
76+
- `ollama` for local model hosting
77+
- `db` for MySQL
78+
- `agent` for the FastAPI application
6179

62-
## 💬 Example Usage
80+
If the model is not downloaded yet, wait for the Ollama container to finish pulling it before sending queries.
6381

64-
After startup, query the agent via HTTP:
82+
### 5. Query the agent
6583

6684
```bash
6785
curl -G --data-urlencode "q=Your query" http://localhost:8000
6886
```
6987

70-
The agent will process your question, generate SQL queries, run it on the MySQL database, and return the result in natural language.
88+
### 6. Check service health
7189

72-
## 🧩 Project Structure
90+
```bash
91+
curl http://localhost:8000/health
92+
```
93+
94+
## AWS Deployment
95+
96+
The AWS path is intentionally simple and cost-focused:
97+
98+
- one EC2 instance
99+
- Docker Compose running `ollama`, `db`, and `agent`
100+
- Terraform-managed VPC, subnet, security group, IAM role, S3 bootstrap bucket, and SSM parameter
101+
102+
This keeps the app easy to understand, but it is still not a production-grade architecture.
103+
104+
### Important cost note
105+
106+
Running Ollama in AWS is much heavier than calling a managed API. The default cloud deployment therefore uses a much smaller model than local mode:
73107

108+
- Local default: `gpt-oss:20b`
109+
- AWS default: `qwen2.5:0.5b-instruct-q5_0`
110+
111+
Even with that change, AWS free-tier compatibility depends on your AWS account type, your region, your storage usage, and how long the instance runs. Treat the provided Terraform defaults as "lowest practical cost", not "guaranteed free".
112+
113+
### 1. Prepare Terraform variables
114+
115+
Move into the Terraform directory:
116+
117+
```bash
118+
cd terraform/aws
74119
```
120+
121+
Create a local variables file from the example:
122+
123+
```bash
124+
cp terraform.tfvars.example terraform.tfvars
125+
```
126+
127+
Update at least these values:
128+
129+
- `project_ref`
130+
- `db_dump_path`
131+
- `db_root_password`
132+
- `app_ingress_cidr_blocks`
133+
134+
For a quick infrastructure smoke test, you can point `db_dump_path` at `../../db/db.sql.example`.
135+
136+
### 2. Review the defaults
137+
138+
The Terraform stack will:
139+
140+
- upload your local SQL dump to a private S3 bucket
141+
- store the MySQL password in SSM Parameter Store
142+
- provision an EC2 instance
143+
- install Docker and Docker Compose on the instance
144+
- clone this repository on the instance
145+
- start the AWS-specific Compose stack from `compose.aws.yaml`
146+
147+
### 3. Deploy
148+
149+
```bash
150+
terraform init
151+
terraform plan
152+
terraform apply
153+
```
154+
155+
After `apply`, Terraform outputs the public API URL and an AWS Systems Manager command you can use to open a shell on the instance.
156+
157+
### 4. Destroy when you are done
158+
159+
```bash
160+
terraform destroy
161+
```
162+
163+
## Configuration
164+
165+
### LLM configuration
166+
167+
- `OLLAMA_MODEL`: model name to use
168+
- `OLLAMA_BASE_URL`: full Ollama base URL such as `http://ollama:11434`
169+
170+
### Database configuration
171+
172+
- `MYSQL_HOST`
173+
- `MYSQL_PORT`
174+
- `MYSQL_DATABASE`
175+
- `MYSQL_USER`
176+
- `MYSQL_ROOT_PASSWORD`
177+
- `MYSQL_ROOT_PASSWORD_FILE`
178+
179+
The application prefers `MYSQL_ROOT_PASSWORD` if it is set, and otherwise reads from `MYSQL_ROOT_PASSWORD_FILE`.
180+
181+
## Project Structure
182+
183+
```text
75184
.
185+
├── compose.yaml
186+
├── compose.aws.yaml
76187
├── db/
77-
│ ├── db.sql # Your MySQL dump file
78-
│ └── passwd.txt # Your MySQL password
79188
├── ollama/
80-
│   ├── entrypoint.sh # Pulls the Ollama model before launching the agent
81-
│   └── ollama/ # Ollama models assets
82-
│   └── ...
83-
├── src/
84-
│   └── sqlagent/ # Source code of the agent
85-
│   └── ...
86-
├── tests/ # Unit tests
87-
│   └── ...
88-
├── compose.yaml # Docker configuration
89-
├── Dockerfile # Docker build instructions for the LLM agent
90-
├── LICENSE # License file
189+
├── src/sqlagent/
190+
├── terraform/aws/
191+
├── tests/
192+
├── Dockerfile
91193
├── README.md
92194
├── pyproject.toml
93-
└── uv.lock # Lockfile generated by uv to ensure deterministic dependency versions
195+
└── uv.lock
94196
```
95197

96-
## 📄 License
198+
## License
97199

98200
This project is distributed under the [MIT License](./LICENSE).

compose.aws.yaml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
services:
2+
ollama:
3+
image: ollama/ollama:latest
4+
restart: unless-stopped
5+
environment:
6+
OLLAMA_MODEL: ${OLLAMA_MODEL:-qwen2.5:0.5b-instruct-q5_0}
7+
volumes:
8+
- ollama-data:/root/.ollama
9+
- ./ollama/entrypoint.sh:/entrypoint.sh:ro
10+
entrypoint: ["/usr/bin/bash", "/entrypoint.sh"]
11+
12+
db:
13+
image: mysql
14+
restart: unless-stopped
15+
environment:
16+
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
17+
MYSQL_DATABASE: ${MYSQL_DATABASE:-sqlagent_db}
18+
volumes:
19+
- mysql-data:/var/lib/mysql
20+
- ./db/db.sql:/docker-entrypoint-initdb.d/db.sql:ro
21+
secrets:
22+
- db_root_password
23+
24+
agent:
25+
build: .
26+
restart: unless-stopped
27+
environment:
28+
OLLAMA_BASE_URL: http://ollama:11434
29+
OLLAMA_MODEL: ${OLLAMA_MODEL:-qwen2.5:0.5b-instruct-q5_0}
30+
MYSQL_HOST: db
31+
MYSQL_PORT: "3306"
32+
MYSQL_DATABASE: ${MYSQL_DATABASE:-sqlagent_db}
33+
MYSQL_USER: ${MYSQL_USER:-root}
34+
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
35+
depends_on:
36+
- ollama
37+
- db
38+
ports:
39+
- "${APP_PORT:-8000}:8000"
40+
volumes:
41+
- ./db/agent-passwd.txt:/run/secrets/db_root_password:ro
42+
healthcheck:
43+
test:
44+
[
45+
"CMD",
46+
"python",
47+
"-c",
48+
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')",
49+
]
50+
interval: 30s
51+
timeout: 10s
52+
retries: 10
53+
start_period: 45s
54+
55+
secrets:
56+
db_root_password:
57+
file: ./db/passwd.txt
58+
59+
volumes:
60+
mysql-data:
61+
ollama-data:

compose.yaml

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,60 @@
11
services:
22
ollama:
33
image: ollama/ollama:latest
4+
restart: unless-stopped
45
environment:
56
OLLAMA_MODEL: ${OLLAMA_MODEL:-gpt-oss:20b}
67
ports:
78
- "11434:11434"
89
volumes:
910
- ./ollama/ollama:/root/.ollama
10-
- ./ollama/entrypoint.sh:/entrypoint.sh
11+
- ./ollama/entrypoint.sh:/entrypoint.sh:ro
1112
entrypoint: ["/usr/bin/bash", "/entrypoint.sh"]
13+
1214
db:
1315
image: mysql
14-
restart: always
16+
restart: unless-stopped
1517
environment:
1618
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
17-
MYSQL_DATABASE: sqlagent_db
19+
MYSQL_DATABASE: ${MYSQL_DATABASE:-sqlagent_db}
1820
ports:
1921
- "3306:3306"
2022
volumes:
21-
- ./db/db.sql:/docker-entrypoint-initdb.d/db.sql
23+
- ./db/db.sql:/docker-entrypoint-initdb.d/db.sql:ro
2224
secrets:
2325
- db_root_password
26+
2427
agent:
2528
build: .
29+
restart: unless-stopped
2630
environment:
27-
OLLAMA_HOST: ollama:11434
31+
OLLAMA_BASE_URL: http://ollama:11434
2832
OLLAMA_MODEL: ${OLLAMA_MODEL:-gpt-oss:20b}
33+
MYSQL_HOST: db
34+
MYSQL_PORT: "3306"
35+
MYSQL_DATABASE: ${MYSQL_DATABASE:-sqlagent_db}
36+
MYSQL_USER: ${MYSQL_USER:-root}
2937
MYSQL_ROOT_PASSWORD_FILE: /run/secrets/db_root_password
3038
depends_on:
3139
- ollama
3240
- db
3341
ports:
34-
- "8000:8000"
42+
- "${APP_PORT:-8000}:8000"
3543
secrets:
3644
- db_root_password
45+
healthcheck:
46+
test:
47+
[
48+
"CMD",
49+
"python",
50+
"-c",
51+
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')",
52+
]
53+
interval: 30s
54+
timeout: 10s
55+
retries: 10
56+
start_period: 45s
57+
3758
secrets:
3859
db_root_password:
3960
file: ./db/passwd.txt

db/.keep

Whitespace-only changes.

0 commit comments

Comments
 (0)