Skip to content

Commit e0049f3

Browse files
refactor to avoid nagivating on form submit
0 parents  commit e0049f3

File tree

12 files changed

+325
-0
lines changed

12 files changed

+325
-0
lines changed

.devcontainer/Dockerfile

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
2+
FROM mcr.microsoft.com/devcontainers/python:alpine3.13

.devcontainer/devcontainer.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"build": {
3+
"dockerfile": "Dockerfile",
4+
"context": ".."
5+
},
6+
"features": {
7+
"ghcr.io/defanglabs/devcontainer-feature/defang-cli:1.0.4": {},
8+
"ghcr.io/devcontainers/features/docker-in-docker:2": {},
9+
"ghcr.io/devcontainers/features/aws-cli:1": {}
10+
}
11+
}

.github/workflows/deploy.yaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
name: Deploy
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
deploy:
10+
environment: playground
11+
runs-on: ubuntu-latest
12+
permissions:
13+
contents: read
14+
id-token: write
15+
16+
steps:
17+
- name: Checkout Repo
18+
uses: actions/checkout@v4
19+
20+
- name: Deploy
21+
uses: DefangLabs/defang-github-action@v1.2.0
22+
with:
23+
config-env-vars: MODEL
24+
env:
25+
MODEL: ${{ secrets.MODEL }}

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.env
2+
myenv
3+
__pycache__/

README.md

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
# Managed LLM with Docker Model Provider
2+
3+
[![1-click-deploy](https://raw.githubusercontent.com/DefangLabs/defang-assets/main/Logos/Buttons/SVG/deploy-with-defang.svg)](https://portal.defang.dev/redirect?url=https%3A%2F%2Fgithub.com%2Fnew%3Ftemplate_name%3Dsample-managed-llm-provider-template%26template_owner%3DDefangSamples)
4+
5+
This sample application demonstrates using Managed LLMs with a Docker Model Provider, deployed with Defang.
6+
7+
> Note: This version uses a [Docker Model Provider](https://docs.docker.com/compose/how-tos/model-runner/#provider-services) for managing LLMs. For the version with Defang's [OpenAI Access Gateway](https://docs.defang.io/docs/concepts/managed-llms/openai-access-gateway), please see our [*Managed LLM Sample*](https://github.com/DefangLabs/samples/tree/main/samples/managed-llm) instead.
8+
9+
The Docker Model Provider allows users to run LLMs locally using `docker compose`. It is a service with `provider:` in the `compose.yaml` file.
10+
Defang will transparently fixup your project to use AWS Bedrock or Google Cloud Vertex AI models during deployment.
11+
12+
You can configure the `LLM_MODEL` and `LLM_URL` for the LLM separately for local development and production environments.
13+
* The `LLM_MODEL` is the LLM Model ID you are using.
14+
* The `LLM_URL` will be set by Docker and during deployment Defang will provide authenticated access to the LLM model in the cloud.
15+
16+
Ensure you have enabled model access for the model you intend to use. To do this, you can check your [AWS Bedrock model access](https://docs.aws.amazon.com/bedrock/latest/userguide/model-access-modify.html) or [GCP Vertex AI model access](https://cloud.google.com/vertex-ai/generative-ai/docs/control-model-access).
17+
18+
To learn about available LLM models in Defang, please see our [Model Mapping documentation](https://docs.defang.io/docs/concepts/managed-llms/openai-access-gateway#model-mapping).
19+
20+
For more about Managed LLMs in Defang, please see our [Managed LLMs documentation](https://docs.defang.io/docs/concepts/managed-llms/managed-language-models).
21+
22+
### Docker Model Provider
23+
24+
In the `compose.yaml` file, the `llm` service will route requests to the LLM API model using a [Docker Model Provider](https://docs.defang.io/docs/concepts/managed-llms/openai-access-gateway#docker-model-provider-services).
25+
26+
The `x-defang-llm` property on the `llm` service must be set to `true` in order to use the Docker Model Provider when deploying with Defang.
27+
28+
## Prerequisites
29+
30+
1. Download [Defang CLI](https://github.com/DefangLabs/defang)
31+
2. (Optional) If you are using [Defang BYOC](https://docs.defang.io/docs/concepts/defang-byoc) authenticate with your cloud provider account
32+
3. (Optional for local development) [Docker CLI](https://docs.docker.com/engine/install/)
33+
34+
## Development
35+
36+
To run the application locally, you can use the following command:
37+
38+
```bash
39+
docker compose -f compose.dev.yaml up --build
40+
```
41+
42+
## Deployment
43+
44+
> [!NOTE]
45+
> Download [Defang CLI](https://github.com/DefangLabs/defang)
46+
47+
### Defang Playground
48+
49+
Deploy your application to the Defang Playground by opening up your terminal and typing:
50+
```bash
51+
defang compose up
52+
```
53+
54+
### BYOC
55+
56+
If you want to deploy to your own cloud account, you can [use Defang BYOC](https://docs.defang.io/docs/tutorials/deploy-to-your-cloud).
57+
58+
---
59+
60+
Title: Managed LLM with Docker Model Provider
61+
62+
Short Description: An app using Managed LLMs with a Docker Model Provider, deployed with Defang.
63+
64+
Tags: LLM, Python, Bedrock, Vertex, Docker Model Provider
65+
66+
Languages: Python

app/.dockerignore

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Default .dockerignore file for Defang
2+
**/__pycache__
3+
**/.direnv
4+
**/.DS_Store
5+
**/.envrc
6+
**/.git
7+
**/.github
8+
**/.idea
9+
**/.next
10+
**/.vscode
11+
**/compose.*.yaml
12+
**/compose.*.yml
13+
**/compose.yaml
14+
**/compose.yml
15+
**/docker-compose.*.yaml
16+
**/docker-compose.*.yml
17+
**/docker-compose.yaml
18+
**/docker-compose.yml
19+
**/node_modules
20+
**/Thumbs.db
21+
Dockerfile
22+
*.Dockerfile
23+
# Ignore our own binary, but only in the root to avoid ignoring subfolders
24+
defang
25+
defang.exe
26+
# Ignore our project-level state
27+
.defang

app/Dockerfile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
FROM python:3.12-slim
2+
3+
# Set working directory
4+
WORKDIR /app
5+
6+
# Copy requirement files first (for better Docker cache)
7+
COPY requirements.txt .
8+
9+
# Install dependencies
10+
RUN pip install --no-cache-dir -r requirements.txt
11+
12+
# Copy the rest of the code
13+
COPY . .
14+
15+
# Expose the port that Uvicorn will run on
16+
EXPOSE 8000
17+
18+
# Set environment variable for the port
19+
ENV PORT=8000
20+
21+
# Run the app with the correct module path using shell form to interpolate environment variable
22+
CMD ["sh", "-c", "uvicorn app:app --host 0.0.0.0 --port $PORT"]

app/app.py

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import json
2+
import logging
3+
import os
4+
5+
import requests
6+
from fastapi import FastAPI, Form, Request
7+
from fastapi.responses import HTMLResponse
8+
from fastapi.staticfiles import StaticFiles
9+
from fastapi.responses import JSONResponse
10+
11+
app = FastAPI()
12+
app.mount("/static", StaticFiles(directory="static"), name="static")
13+
14+
# Configure basic logging
15+
logging.basicConfig(level=logging.INFO)
16+
17+
default_openai_base_url = "https://api.openai.com/v1/"
18+
19+
# Set the environment variables for the chat model
20+
LLM_URL = os.getenv("LLM_URL", default_openai_base_url) + "chat/completions"
21+
# Fallback to OpenAI Model if not set in environment
22+
MODEL_ID = os.getenv("LLM_MODEL", "gpt-4-turbo")
23+
24+
# Get the API key for the LLM
25+
# For development, you can use your local API key. In production, the LLM gateway service will override the need for it.
26+
def get_api_key():
27+
return os.getenv("OPENAI_API_KEY", "")
28+
29+
# Home page form
30+
@app.get("/", response_class=HTMLResponse)
31+
async def home():
32+
return """
33+
<html>
34+
<head>
35+
<title>Ask the AI Model</title>
36+
<script type="text/javascript" src="./static/app.js"></script>
37+
</head>
38+
<body>
39+
<h1>Ask the AI Model</h1>
40+
<form method="post" id="askForm" onsubmit="event.preventDefault(); submitForm(event);">
41+
<textarea id="prompt" name="prompt" autofocus="autofocus" rows="5" cols="60" placeholder="Enter your question here..."
42+
onkeydown="if(event.key==='Enter'&&!event.shiftKey){event.preventDefault();this.form.dispatchEvent(new Event('submit', {cancelable:true}));}"></textarea>
43+
<br><br>
44+
<input type="submit" value="Ask">
45+
</form>
46+
<hr>
47+
<h2>Model's Reply:</h2>
48+
<p id="reply"></p>
49+
</body>
50+
</html>
51+
"""
52+
53+
# Handle form submission
54+
@app.post("/ask", response_class=JSONResponse)
55+
async def ask(prompt: str = Form(...)):
56+
payload = {
57+
"model": MODEL_ID,
58+
"messages": [
59+
{"role": "user", "content": prompt}
60+
],
61+
"stream": False
62+
}
63+
64+
reply = get_llm_response(payload)
65+
66+
return {"prompt": prompt, "reply": reply}
67+
68+
def get_llm_response(payload):
69+
api_key = get_api_key()
70+
request_headers = {
71+
"Content-Type": "application/json",
72+
"Authorization": f"Bearer {api_key}"
73+
}
74+
75+
# Log request details
76+
logging.info(f"Sending POST to {LLM_URL}")
77+
logging.info(f"Request Headers: {request_headers}")
78+
logging.info(f"Request Payload: {payload}")
79+
80+
response = None
81+
try:
82+
response = requests.post(f"{LLM_URL}", headers=request_headers, data=json.dumps(payload))
83+
except requests.exceptions.HTTPError as errh:
84+
return f"HTTP error:", errh
85+
except requests.exceptions.ConnectionError as errc:
86+
return f"Connection error:", errc
87+
except requests.exceptions.Timeout as errt:
88+
return f"Timeout error:", errt
89+
except requests.exceptions.RequestException as err:
90+
return f"Unexpected error:", err
91+
92+
if response is None:
93+
return f"Error: No response from server."
94+
if response.status_code == 400:
95+
return f"Connect Error: {response.status_code} - {response.text}"
96+
if response.status_code == 500:
97+
return f"Error from server: {response.status_code} - {response.text}"
98+
99+
try:
100+
data = response.json()
101+
return data["choices"][0]["message"]["content"]
102+
except (KeyError, IndexError):
103+
return "Model returned an unexpected response."

app/requirements.txt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
dotenv
2+
fastapi
3+
python-multipart
4+
requests
5+
uvicorn

app/static/app.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
async function submitForm(event) {
2+
event.preventDefault();
3+
const prompt = document.getElementById('prompt').value;
4+
document.getElementById('reply').innerHTML = "Loading...";
5+
const response = await fetch('/ask', {
6+
method: 'POST',
7+
headers: {
8+
'Content-Type': 'application/x-www-form-urlencoded'
9+
},
10+
body: new URLSearchParams({prompt})
11+
});
12+
const data = await response.json();
13+
document.getElementById('reply').innerHTML = data.reply || "No reply found.";
14+
}

0 commit comments

Comments
 (0)