Skip to content

Commit c7b3712

Browse files
fix env var names
0 parents  commit c7b3712

File tree

13 files changed

+325
-0
lines changed

13 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.local.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: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
import json
2+
import logging
3+
import os
4+
5+
import requests
6+
from fastapi import FastAPI, Form
7+
from fastapi.responses import HTMLResponse
8+
from fastapi.staticfiles import StaticFiles
9+
from fastapi.responses import JSONResponse
10+
from fastapi.responses import FileResponse
11+
12+
app = FastAPI()
13+
app.mount("/static", StaticFiles(directory="static"), name="static")
14+
15+
# Configure basic logging
16+
logging.basicConfig(level=logging.INFO)
17+
18+
default_openai_base_url = "https://api.openai.com/v1/"
19+
20+
# Set the environment variables for the chat model
21+
LLM_URL = os.getenv("LLM_URL", default_openai_base_url) + "chat/completions"
22+
# Fallback to OpenAI Model if not set in environment
23+
MODEL_ID = os.getenv("LLM_MODEL", "gpt-4-turbo")
24+
25+
# Get the API key for the LLM
26+
# For development, you have the option to use your local API key. In production, the LLM gateway service will override the need for it.
27+
def get_api_key():
28+
return os.getenv("OPENAI_API_KEY", "")
29+
30+
# Home page form
31+
@app.get("/", response_class=HTMLResponse)
32+
async def home():
33+
return FileResponse("static/index.html", media_type="text/html")
34+
35+
# Handle form submission
36+
@app.post("/ask", response_class=JSONResponse)
37+
async def ask(prompt: str = Form(...)):
38+
payload = {
39+
"model": MODEL_ID,
40+
"messages": [
41+
{"role": "user", "content": prompt}
42+
],
43+
"stream": False
44+
}
45+
46+
reply = get_llm_response(payload)
47+
48+
return {"prompt": prompt, "reply": reply}
49+
50+
def get_llm_response(payload):
51+
api_key = get_api_key()
52+
request_headers = {
53+
"Content-Type": "application/json",
54+
"Authorization": f"Bearer {api_key}"
55+
}
56+
57+
# Log request details
58+
logging.info(f"Sending POST to {LLM_URL}")
59+
logging.info(f"Request Headers: {request_headers}")
60+
logging.info(f"Request Payload: {payload}")
61+
62+
response = None
63+
try:
64+
response = requests.post(f"{LLM_URL}", headers=request_headers, data=json.dumps(payload))
65+
except requests.exceptions.HTTPError as errh:
66+
return f"HTTP error:", errh
67+
except requests.exceptions.ConnectionError as errc:
68+
return f"Connection error:", errc
69+
except requests.exceptions.Timeout as errt:
70+
return f"Timeout error:", errt
71+
except requests.exceptions.RequestException as err:
72+
return f"Unexpected error:", err
73+
74+
if response is None:
75+
return f"Error: No response from server."
76+
if response.status_code == 400:
77+
return f"Connect Error: {response.status_code} - {response.text}"
78+
if response.status_code == 500:
79+
return f"Error from server: {response.status_code} - {response.text}"
80+
81+
try:
82+
data = response.json()
83+
return data["choices"][0]["message"]["content"]
84+
except (KeyError, IndexError):
85+
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)