Compare commits
10 Commits
88ecb458e1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
acf6421b53
|
|||
|
875795a409
|
|||
|
b9d1c2a9a3
|
|||
|
6f8b7ffca6
|
|||
|
cc75227a77
|
|||
|
9ae82fc3de
|
|||
|
92edf49948
|
|||
|
25d3a7805c
|
|||
|
eb67191706
|
|||
|
d51560f979
|
208
active/container_bifrost/bifrost.md
Normal file
208
active/container_bifrost/bifrost.md
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
# Podman bifrost
|
||||||
|
|
||||||
|
- [Podman bifrost](#podman-bifrost)
|
||||||
|
- [Setup bifrost Project](#setup-bifrost-project)
|
||||||
|
- [Install bifrost](#install-bifrost)
|
||||||
|
- [Create the ai user](#create-the-ai-user)
|
||||||
|
- [Write the bifrost compose spec](#write-the-bifrost-compose-spec)
|
||||||
|
- [A Note on Volumes](#a-note-on-volumes)
|
||||||
|
- [Convert bifrost compose spec to quadlets](#convert-bifrost-compose-spec-to-quadlets)
|
||||||
|
- [Start and enable your systemd quadlet](#start-and-enable-your-systemd-quadlet)
|
||||||
|
- [Expose bifrost](#expose-bifrost)
|
||||||
|
- [Using bifrost](#using-bifrost)
|
||||||
|
- [Adding Models](#adding-models)
|
||||||
|
- [Testing Models](#testing-models)
|
||||||
|
- [Backup bifrost](#backup-bifrost)
|
||||||
|
- [Upgrade bifrost](#upgrade-bifrost)
|
||||||
|
- [Upgrade Quadlets](#upgrade-quadlets)
|
||||||
|
- [Uninstall](#uninstall)
|
||||||
|
- [Notes](#notes)
|
||||||
|
- [SELinux](#selinux)
|
||||||
|
|
||||||
|
## Setup bifrost Project
|
||||||
|
|
||||||
|
- [ ] Copy and rename this folder to active/container_bifrost
|
||||||
|
- [ ] Find and replace bifrost with the name of the service.
|
||||||
|
- [ ] Create the rootless user to run the podman containers
|
||||||
|
- [ ] Write the compose.yaml spec for your service
|
||||||
|
- [ ] Convert the compose.yaml spec to a quadlet
|
||||||
|
- [ ] Install the quadlet on the podman server
|
||||||
|
- [ ] Expose the quadlet service
|
||||||
|
- [ ] Install a backup service and timer
|
||||||
|
|
||||||
|
## Install bifrost
|
||||||
|
|
||||||
|
### Create the ai user
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH into your podman server as root
|
||||||
|
useradd ai
|
||||||
|
loginctl enable-linger $(id -u ai)
|
||||||
|
systemctl --user --machine=ai@.host enable podman-restart
|
||||||
|
systemctl --user --machine=ai@.host enable --now podman.socket
|
||||||
|
mkdir -p /home/ai/.config/containers/systemd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Write the bifrost compose spec
|
||||||
|
|
||||||
|
Edit the compose.yaml at active/container_bifrost/compose/compose.yaml
|
||||||
|
|
||||||
|
#### A Note on Volumes
|
||||||
|
|
||||||
|
Named volumes are stored at `/home/bifrost/.local/share/containers/storage/volumes/`.
|
||||||
|
|
||||||
|
### Convert bifrost compose spec to quadlets
|
||||||
|
|
||||||
|
Run the following to convert a compose.yaml into the various `.container` files for systemd:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate the systemd service
|
||||||
|
podman run \
|
||||||
|
--security-opt label=disable \
|
||||||
|
--rm \
|
||||||
|
-v $(pwd)/active/container_bifrost/compose:/compose \
|
||||||
|
-v $(pwd)/active/container_bifrost/quadlets:/quadlets \
|
||||||
|
quay.io/k9withabone/podlet \
|
||||||
|
-f /quadlets \
|
||||||
|
-i \
|
||||||
|
--overwrite \
|
||||||
|
compose /compose/compose.yaml
|
||||||
|
|
||||||
|
# Copy the files to the server
|
||||||
|
export PODMAN_SERVER=ai-ai
|
||||||
|
scp -r active/container_bifrost/quadlets/. $PODMAN_SERVER:/home/ai/.config/containers/systemd/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start and enable your systemd quadlet
|
||||||
|
|
||||||
|
SSH into your podman server as root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user restart bifrost
|
||||||
|
journalctl --user -u bifrost -f
|
||||||
|
# Enable auto-update service which will pull new container images automatically every day
|
||||||
|
systemctl --user enable --now podman-auto-update.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Expose bifrost
|
||||||
|
|
||||||
|
1. If you need a domain, follow the [DDNS instructions](/active/container_ddns/ddns.md#install-a-new-ddns-service)
|
||||||
|
2. For a web service, follow the [Caddy instructions](/active/container_caddy/caddy.md#adding-a-new-caddy-record)
|
||||||
|
3. Finally, follow your OS's guide for opening ports via its firewall service.
|
||||||
|
|
||||||
|
## Using bifrost
|
||||||
|
|
||||||
|
### Adding Models
|
||||||
|
|
||||||
|
```json
|
||||||
|
// qwen3.5-35b-a3b-thinking
|
||||||
|
{
|
||||||
|
"temperature": 1,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"presence_penalty": 1.5,
|
||||||
|
"extra_body": {
|
||||||
|
"top_k": 20,
|
||||||
|
"min_p": 0,
|
||||||
|
"repetition_penalty": 1,
|
||||||
|
"chat_template_kwargs": {
|
||||||
|
"enable_thinking": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// qwen3.5-35b-a3b-coding
|
||||||
|
{
|
||||||
|
"temperature": 0.6,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"presence_penalty": 0,
|
||||||
|
"extra_body": {
|
||||||
|
"top_k": 20,
|
||||||
|
"min_p": 0,
|
||||||
|
"repetition_penalty": 1,
|
||||||
|
"chat_template_kwargs": {
|
||||||
|
"enable_thinking": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// qwen3.5-35b-a3b-instruct
|
||||||
|
{
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_p": 0.8,
|
||||||
|
"presence_penalty": 1.5,
|
||||||
|
"extra_body": {
|
||||||
|
"top_k": 20,
|
||||||
|
"min_p": 0,
|
||||||
|
"repetition_penalty": 1,
|
||||||
|
"chat_template_kwargs": {
|
||||||
|
"enable_thinking": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Models
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List models
|
||||||
|
curl -L -X GET 'https://aipi.reeseapps.com/v1/models' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Authorization: Bearer sk-1234'
|
||||||
|
|
||||||
|
curl -L -X POST 'https://aipi.reeseapps.com/v1/chat/completions' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Authorization: Bearer sk-1234' \
|
||||||
|
-d '{
|
||||||
|
"model": "gpt-4o-mini", # 👈 REPLACE with 'public model name' for any db-model
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"content": "Hey, how's it going",
|
||||||
|
"role": "user"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup bifrost
|
||||||
|
|
||||||
|
Follow the [Borg Backup instructions](/active/systemd_borg/borg.md#set-up-a-client-for-backup)
|
||||||
|
|
||||||
|
## Upgrade bifrost
|
||||||
|
|
||||||
|
### Upgrade Quadlets
|
||||||
|
|
||||||
|
Upgrades should be a repeat of [writing the compose spec](#convert-bifrost-compose-spec-to-quadlets) and [installing the quadlets](#start-and-enable-your-systemd-quadlet)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PODMAN_SERVER=
|
||||||
|
scp -r quadlets/. $PODMAN_SERVER$:/home/bifrost/.config/containers/systemd/
|
||||||
|
ssh bifrost systemctl --user daemon-reload
|
||||||
|
ssh bifrost systemctl --user restart bifrost
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uninstall
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop the user's services
|
||||||
|
systemctl --user disable podman-restart
|
||||||
|
podman container stop --all
|
||||||
|
systemctl --user disable --now podman.socket
|
||||||
|
systemctl --user disable --now podman-auto-update.timer
|
||||||
|
|
||||||
|
# Delete the user (this won't delete their home directory)
|
||||||
|
# userdel might spit out an error like:
|
||||||
|
# userdel: user bifrost is currently used by process 591255
|
||||||
|
# kill those processes and try again
|
||||||
|
userdel bifrost
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
### SELinux
|
||||||
|
|
||||||
|
<https://blog.christophersmart.com/2021/01/31/podman-volumes-and-selinux/>
|
||||||
|
|
||||||
|
:z allows a container to share a mounted volume with all other containers.
|
||||||
|
|
||||||
|
:Z allows a container to reserve a mounted volume and prevents any other container from accessing.
|
||||||
3
active/container_bifrost/compose/README.md
Normal file
3
active/container_bifrost/compose/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Compose
|
||||||
|
|
||||||
|
Put your compose.yaml here.
|
||||||
32
active/container_bifrost/compose/compose.yaml
Normal file
32
active/container_bifrost/compose/compose.yaml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
services:
|
||||||
|
bifrost:
|
||||||
|
image: docker.io/maximhq/bifrost:latest
|
||||||
|
container_name: bifrost
|
||||||
|
ports:
|
||||||
|
- "8000:8000"
|
||||||
|
volumes:
|
||||||
|
- bifrost-data:/app/data
|
||||||
|
environment:
|
||||||
|
- APP_PORT=8000
|
||||||
|
- APP_HOST=0.0.0.0
|
||||||
|
- LOG_LEVEL=info
|
||||||
|
- LOG_STYLE=json
|
||||||
|
ulimits:
|
||||||
|
nofile:
|
||||||
|
soft: 65536
|
||||||
|
hard: 65536
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
[
|
||||||
|
"CMD",
|
||||||
|
"wget",
|
||||||
|
"--no-verbose",
|
||||||
|
"--tries=1",
|
||||||
|
"-O",
|
||||||
|
"/dev/null",
|
||||||
|
"http://localhost:8080/health",
|
||||||
|
]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
restart: unless-stopped
|
||||||
17
active/container_bifrost/quadlets/bifrost.container
Normal file
17
active/container_bifrost/quadlets/bifrost.container
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
[Container]
|
||||||
|
ContainerName=bifrost
|
||||||
|
Environment=APP_PORT=8000 APP_HOST=0.0.0.0 LOG_LEVEL=info LOG_STYLE=json
|
||||||
|
HealthCmd=["wget", "--no-verbose", "--tries=1", "-O", "/dev/null", "http://localhost:8080/health"]
|
||||||
|
HealthInterval=30s
|
||||||
|
HealthRetries=3
|
||||||
|
HealthTimeout=10s
|
||||||
|
Image=docker.io/maximhq/bifrost:latest
|
||||||
|
PublishPort=8000:8000
|
||||||
|
Ulimit=nofile=65536:65536
|
||||||
|
Volume=bifrost-data:/app/data
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -70,6 +70,11 @@ active/container_caddy/install_caddy_proxy.yaml
|
|||||||
ansible-playbook \
|
ansible-playbook \
|
||||||
-i ansible/inventory.yaml \
|
-i ansible/inventory.yaml \
|
||||||
active/container_caddy/install_caddy_deskwork.yaml
|
active/container_caddy/install_caddy_deskwork.yaml
|
||||||
|
|
||||||
|
# Toybox (AI) Proxy
|
||||||
|
ansible-playbook \
|
||||||
|
-i ansible/inventory.yaml \
|
||||||
|
active/container_caddy/install_caddy_toybox.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
See ansible playbook [install_caddy.yaml](/active/container_caddy/install_caddy.yaml)
|
See ansible playbook [install_caddy.yaml](/active/container_caddy/install_caddy.yaml)
|
||||||
|
|||||||
28
active/container_caddy/install_caddy_toybox.yaml
Normal file
28
active/container_caddy/install_caddy_toybox.yaml
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
- name: Create Caddy Proxy
|
||||||
|
hosts: toybox-root
|
||||||
|
tasks:
|
||||||
|
- name: Create /etc/caddy dir
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /etc/caddy
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
- name: Copy Caddyfile
|
||||||
|
template:
|
||||||
|
src: secrets/toybox.Caddyfile
|
||||||
|
dest: /etc/caddy/Caddyfile
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
- name: Template Caddy Container Services
|
||||||
|
template:
|
||||||
|
src: caddy.container
|
||||||
|
dest: /etc/containers/systemd/caddy.container
|
||||||
|
owner: root
|
||||||
|
group: root
|
||||||
|
mode: "0644"
|
||||||
|
- name: Reload and start the Caddy service
|
||||||
|
ansible.builtin.systemd_service:
|
||||||
|
state: restarted
|
||||||
|
name: caddy.service
|
||||||
|
enabled: true
|
||||||
|
daemon_reload: true
|
||||||
3
active/container_litellm/compose/README.md
Normal file
3
active/container_litellm/compose/README.md
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Compose
|
||||||
|
|
||||||
|
Put your compose.yaml here.
|
||||||
37
active/container_litellm/compose/compose.yaml
Normal file
37
active/container_litellm/compose/compose.yaml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
services:
|
||||||
|
litellm:
|
||||||
|
image: docker.litellm.ai/berriai/litellm:main-latest
|
||||||
|
ports:
|
||||||
|
- 4000:4000
|
||||||
|
env_file: /home/ai/litellm.env
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: "postgresql://llmproxy:dbpassword9090@host.containers.internal:5432/litellm"
|
||||||
|
STORE_MODEL_IN_DB: "True"
|
||||||
|
restart: unless-stopped
|
||||||
|
depends_on:
|
||||||
|
- litellm-db # Indicates that this service depends on the 'litellm-db' service, ensuring 'litellm-db' starts first
|
||||||
|
healthcheck: # Defines the health check configuration for the container
|
||||||
|
test:
|
||||||
|
- CMD-SHELL
|
||||||
|
- python3 -c "import urllib.request; urllib.request.urlopen('http://localhost:4000/health/liveliness')" # Command to execute for health check
|
||||||
|
interval: 30s # Perform health check every 30 seconds
|
||||||
|
timeout: 10s # Health check command times out after 10 seconds
|
||||||
|
retries: 3 # Retry up to 3 times if health check fails
|
||||||
|
start_period: 40s # Wait 40 seconds after container start before beginning health checks
|
||||||
|
|
||||||
|
litellm-db:
|
||||||
|
image: docker.io/postgres:16
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
POSTGRES_DB: litellm
|
||||||
|
POSTGRES_USER: llmproxy
|
||||||
|
POSTGRES_PASSWORD: dbpassword9090
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
volumes:
|
||||||
|
- litellm_postgres_data:/var/lib/postgresql/data:z
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -d litellm -U llmproxy"]
|
||||||
|
interval: 1s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
67
active/container_litellm/config.yaml
Normal file
67
active/container_litellm/config.yaml
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
# General settings
|
||||||
|
|
||||||
|
general_settings:
|
||||||
|
request_timeout: 600
|
||||||
|
|
||||||
|
# Models
|
||||||
|
model_list:
|
||||||
|
# Qwen3.5-35B variants
|
||||||
|
- model_name: qwen3.5-35b-think-general
|
||||||
|
litellm_params:
|
||||||
|
model: openai/qwen3.5-35b-a3b
|
||||||
|
api_base: https://llama-cpp.reeselink.com
|
||||||
|
api_key: none
|
||||||
|
temperature: 1.0
|
||||||
|
top_p: 0.95
|
||||||
|
presence_penalty: 1.5
|
||||||
|
extra_body:
|
||||||
|
top_k: 20
|
||||||
|
min_p: 0.0
|
||||||
|
repetition_penalty: 1.0
|
||||||
|
chat_template_kwargs:
|
||||||
|
enable_thinking: true
|
||||||
|
|
||||||
|
- model_name: qwen3.5-35b-think-code
|
||||||
|
litellm_params:
|
||||||
|
model: openai/qwen3.5-35b-a3b
|
||||||
|
api_base: https://llama-cpp.reeselink.com
|
||||||
|
api_key: none
|
||||||
|
temperature: 0.6
|
||||||
|
top_p: 0.95
|
||||||
|
presence_penalty: 0.0
|
||||||
|
extra_body:
|
||||||
|
top_k: 20
|
||||||
|
min_p: 0.0
|
||||||
|
repetition_penalty: 1.0
|
||||||
|
chat_template_kwargs:
|
||||||
|
enable_thinking: true
|
||||||
|
|
||||||
|
- model_name: qwen3.5-35b-instruct-general
|
||||||
|
litellm_params:
|
||||||
|
model: openai/qwen3.5-35b-a3b
|
||||||
|
api_base: https://llama-cpp.reeselink.com
|
||||||
|
api_key: none
|
||||||
|
temperature: 0.7
|
||||||
|
top_p: 0.8
|
||||||
|
presence_penalty: 1.5
|
||||||
|
extra_body:
|
||||||
|
top_k: 20
|
||||||
|
min_p: 0.0
|
||||||
|
repetition_penalty: 1.0
|
||||||
|
chat_template_kwargs:
|
||||||
|
enable_thinking: false
|
||||||
|
|
||||||
|
- model_name: qwen3.5-35b-instruct-reasoning
|
||||||
|
litellm_params:
|
||||||
|
model: openai/qwen3.5-35b-a3b
|
||||||
|
api_base: https://llama-cpp.reeselink.com
|
||||||
|
api_key: none
|
||||||
|
temperature: 1.0
|
||||||
|
top_p: 0.95
|
||||||
|
presence_penalty: 1.5
|
||||||
|
extra_body:
|
||||||
|
top_k: 20
|
||||||
|
min_p: 0.0
|
||||||
|
repetition_penalty: 1.0
|
||||||
|
chat_template_kwargs:
|
||||||
|
enable_thinking: false
|
||||||
233
active/container_litellm/litellm.md
Normal file
233
active/container_litellm/litellm.md
Normal file
@@ -0,0 +1,233 @@
|
|||||||
|
# Podman litellm
|
||||||
|
|
||||||
|
- [Podman litellm](#podman-litellm)
|
||||||
|
- [Setup litellm Project](#setup-litellm-project)
|
||||||
|
- [Install litellm](#install-litellm)
|
||||||
|
- [Create the ai user](#create-the-ai-user)
|
||||||
|
- [Write the litellm compose spec](#write-the-litellm-compose-spec)
|
||||||
|
- [A Note on Volumes](#a-note-on-volumes)
|
||||||
|
- [Convert litellm compose spec to quadlets](#convert-litellm-compose-spec-to-quadlets)
|
||||||
|
- [Create the litellm.env file](#create-the-litellmenv-file)
|
||||||
|
- [Start and enable your systemd quadlet](#start-and-enable-your-systemd-quadlet)
|
||||||
|
- [Expose litellm](#expose-litellm)
|
||||||
|
- [Using LiteLLM](#using-litellm)
|
||||||
|
- [Adding Models](#adding-models)
|
||||||
|
- [Testing Models](#testing-models)
|
||||||
|
- [Backup litellm](#backup-litellm)
|
||||||
|
- [Upgrade litellm](#upgrade-litellm)
|
||||||
|
- [Upgrade Quadlets](#upgrade-quadlets)
|
||||||
|
- [Uninstall](#uninstall)
|
||||||
|
- [Notes](#notes)
|
||||||
|
- [SELinux](#selinux)
|
||||||
|
|
||||||
|
## Setup litellm Project
|
||||||
|
|
||||||
|
- [ ] Copy and rename this folder to active/container_litellm
|
||||||
|
- [ ] Find and replace litellm with the name of the service.
|
||||||
|
- [ ] Create the rootless user to run the podman containers
|
||||||
|
- [ ] Write the compose.yaml spec for your service
|
||||||
|
- [ ] Convert the compose.yaml spec to a quadlet
|
||||||
|
- [ ] Install the quadlet on the podman server
|
||||||
|
- [ ] Expose the quadlet service
|
||||||
|
- [ ] Install a backup service and timer
|
||||||
|
|
||||||
|
## Install litellm
|
||||||
|
|
||||||
|
### Create the ai user
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# SSH into your podman server as root
|
||||||
|
useradd ai
|
||||||
|
loginctl enable-linger $(id -u ai)
|
||||||
|
systemctl --user --machine=ai@.host enable podman-restart
|
||||||
|
systemctl --user --machine=ai@.host enable --now podman.socket
|
||||||
|
mkdir -p /home/ai/.config/containers/systemd
|
||||||
|
```
|
||||||
|
|
||||||
|
### Write the litellm compose spec
|
||||||
|
|
||||||
|
See the [docker run command here](https://docs.litellm.ai/docs/proxy/docker_quick_start#32-start-proxy)
|
||||||
|
|
||||||
|
Edit the compose.yaml at active/container_litellm/compose/compose.yaml
|
||||||
|
|
||||||
|
#### A Note on Volumes
|
||||||
|
|
||||||
|
Named volumes are stored at `/home/litellm/.local/share/containers/storage/volumes/`.
|
||||||
|
|
||||||
|
### Convert litellm compose spec to quadlets
|
||||||
|
|
||||||
|
Run the following to convert a compose.yaml into the various `.container` files for systemd:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate the systemd service
|
||||||
|
podman run \
|
||||||
|
--security-opt label=disable \
|
||||||
|
--rm \
|
||||||
|
-v $(pwd)/active/container_litellm/compose:/compose \
|
||||||
|
-v $(pwd)/active/container_litellm/quadlets:/quadlets \
|
||||||
|
quay.io/k9withabone/podlet \
|
||||||
|
-f /quadlets \
|
||||||
|
-i \
|
||||||
|
--overwrite \
|
||||||
|
compose /compose/compose.yaml
|
||||||
|
|
||||||
|
# Copy the files to the server
|
||||||
|
export PODMAN_SERVER=ai-ai
|
||||||
|
scp -r active/container_litellm/quadlets/. $PODMAN_SERVER:/home/ai/.config/containers/systemd/
|
||||||
|
```
|
||||||
|
|
||||||
|
### Create the litellm.env file
|
||||||
|
|
||||||
|
Should look something like:
|
||||||
|
|
||||||
|
```env
|
||||||
|
LITELLM_MASTER_KEY="random-string"
|
||||||
|
LITELLM_SALT_KEY="random-string"
|
||||||
|
|
||||||
|
UI_USERNAME="admin"
|
||||||
|
UI_PASSWORD="random-string"
|
||||||
|
```
|
||||||
|
|
||||||
|
Then copy it to the server
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PODMAN_SERVER=ai
|
||||||
|
scp -r active/container_litellm/config.yaml $PODMAN_SERVER:/home/ai/litellm_config.yaml
|
||||||
|
ssh $PODMAN_SERVER chown -R ai:ai /home/ai/litellm_config.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Start and enable your systemd quadlet
|
||||||
|
|
||||||
|
SSH into your podman server as root:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh ai
|
||||||
|
machinectl shell ai@
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user restart litellm
|
||||||
|
journalctl --user -u litellm -f
|
||||||
|
# Enable auto-update service which will pull new container images automatically every day
|
||||||
|
systemctl --user enable --now podman-auto-update.timer
|
||||||
|
```
|
||||||
|
|
||||||
|
### Expose litellm
|
||||||
|
|
||||||
|
1. If you need a domain, follow the [DDNS instructions](/active/container_ddns/ddns.md#install-a-new-ddns-service)
|
||||||
|
2. For a web service, follow the [Caddy instructions](/active/container_caddy/caddy.md#adding-a-new-caddy-record)
|
||||||
|
3. Finally, follow your OS's guide for opening ports via its firewall service.
|
||||||
|
|
||||||
|
## Using LiteLLM
|
||||||
|
|
||||||
|
### Adding Models
|
||||||
|
|
||||||
|
```json
|
||||||
|
// qwen3.5-35b-a3b-thinking
|
||||||
|
{
|
||||||
|
"temperature": 1,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"presence_penalty": 1.5,
|
||||||
|
"extra_body": {
|
||||||
|
"top_k": 20,
|
||||||
|
"min_p": 0,
|
||||||
|
"repetition_penalty": 1,
|
||||||
|
"chat_template_kwargs": {
|
||||||
|
"enable_thinking": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// qwen3.5-35b-a3b-coding
|
||||||
|
{
|
||||||
|
"temperature": 0.6,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"presence_penalty": 0,
|
||||||
|
"extra_body": {
|
||||||
|
"top_k": 20,
|
||||||
|
"min_p": 0,
|
||||||
|
"repetition_penalty": 1,
|
||||||
|
"chat_template_kwargs": {
|
||||||
|
"enable_thinking": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// qwen3.5-35b-a3b-instruct
|
||||||
|
{
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_p": 0.8,
|
||||||
|
"presence_penalty": 1.5,
|
||||||
|
"extra_body": {
|
||||||
|
"top_k": 20,
|
||||||
|
"min_p": 0,
|
||||||
|
"repetition_penalty": 1,
|
||||||
|
"chat_template_kwargs": {
|
||||||
|
"enable_thinking": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Models
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# List models
|
||||||
|
curl -L -X GET 'https://aipi.reeseapps.com/v1/models' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Authorization: Bearer sk-1234'
|
||||||
|
|
||||||
|
curl -L -X POST 'https://aipi.reeseapps.com/v1/chat/completions' \
|
||||||
|
-H 'Content-Type: application/json' \
|
||||||
|
-H 'Authorization: Bearer sk-1234' \
|
||||||
|
-d '{
|
||||||
|
"model": "gpt-4o-mini", # 👈 REPLACE with 'public model name' for any db-model
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"content": "Hey, how's it going",
|
||||||
|
"role": "user"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Backup litellm
|
||||||
|
|
||||||
|
Follow the [Borg Backup instructions](/active/systemd_borg/borg.md#set-up-a-client-for-backup)
|
||||||
|
|
||||||
|
## Upgrade litellm
|
||||||
|
|
||||||
|
### Upgrade Quadlets
|
||||||
|
|
||||||
|
Upgrades should be a repeat of [writing the compose spec](#convert-litellm-compose-spec-to-quadlets) and [installing the quadlets](#start-and-enable-your-systemd-quadlet)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export PODMAN_SERVER=
|
||||||
|
scp -r quadlets/. $PODMAN_SERVER$:/home/litellm/.config/containers/systemd/
|
||||||
|
ssh litellm systemctl --user daemon-reload
|
||||||
|
ssh litellm systemctl --user restart litellm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Uninstall
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stop the user's services
|
||||||
|
systemctl --user disable podman-restart
|
||||||
|
podman container stop --all
|
||||||
|
systemctl --user disable --now podman.socket
|
||||||
|
systemctl --user disable --now podman-auto-update.timer
|
||||||
|
|
||||||
|
# Delete the user (this won't delete their home directory)
|
||||||
|
# userdel might spit out an error like:
|
||||||
|
# userdel: user litellm is currently used by process 591255
|
||||||
|
# kill those processes and try again
|
||||||
|
userdel litellm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
|
||||||
|
### SELinux
|
||||||
|
|
||||||
|
<https://blog.christophersmart.com/2021/01/31/podman-volumes-and-selinux/>
|
||||||
|
|
||||||
|
:z allows a container to share a mounted volume with all other containers.
|
||||||
|
|
||||||
|
:Z allows a container to reserve a mounted volume and prevents any other container from accessing.
|
||||||
15
active/container_litellm/quadlets/litellm-db.container
Normal file
15
active/container_litellm/quadlets/litellm-db.container
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[Container]
|
||||||
|
Environment=POSTGRES_DB=litellm POSTGRES_USER=llmproxy POSTGRES_PASSWORD=dbpassword9090
|
||||||
|
HealthCmd='pg_isready -d litellm -U llmproxy'
|
||||||
|
HealthInterval=1s
|
||||||
|
HealthRetries=10
|
||||||
|
HealthTimeout=5s
|
||||||
|
Image=docker.io/postgres:16
|
||||||
|
PublishPort=5432:5432
|
||||||
|
Volume=litellm_postgres_data:/var/lib/postgresql/data:z
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
19
active/container_litellm/quadlets/litellm.container
Normal file
19
active/container_litellm/quadlets/litellm.container
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
[Unit]
|
||||||
|
Requires=litellm-db.service
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
Environment=DATABASE_URL=postgresql://llmproxy:dbpassword9090@host.containers.internal:5432/litellm STORE_MODEL_IN_DB=True
|
||||||
|
EnvironmentFile=/home/ai/litellm.env
|
||||||
|
HealthCmd="python3 -c \"import urllib.request; urllib.request.urlopen('http://localhost:4000/health/liveliness')\""
|
||||||
|
HealthInterval=30s
|
||||||
|
HealthRetries=3
|
||||||
|
HealthStartPeriod=40s
|
||||||
|
HealthTimeout=10s
|
||||||
|
Image=docker.litellm.ai/berriai/litellm:main-latest
|
||||||
|
PublishPort=4000:4000
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
- [Important Locations](#important-locations)
|
- [Important Locations](#important-locations)
|
||||||
- [Monitoring Scripts](#monitoring-scripts)
|
- [Monitoring Scripts](#monitoring-scripts)
|
||||||
- [Quick Ansible Commands](#quick-ansible-commands)
|
- [Quick Ansible Commands](#quick-ansible-commands)
|
||||||
|
- [Quickstart VM](#quickstart-vm)
|
||||||
- [Disk Mounts](#disk-mounts)
|
- [Disk Mounts](#disk-mounts)
|
||||||
- [Disk Performance Testing](#disk-performance-testing)
|
- [Disk Performance Testing](#disk-performance-testing)
|
||||||
- [General VM Notes](#general-vm-notes)
|
- [General VM Notes](#general-vm-notes)
|
||||||
@@ -45,6 +46,35 @@ ansible-playbook -i ansible/inventory.yaml -l proxy active/container_caddy/insta
|
|||||||
ansible-playbook -i ansible/inventory.yaml -l proxy active/container_ddns/install_ddns.yaml
|
ansible-playbook -i ansible/inventory.yaml -l proxy active/container_ddns/install_ddns.yaml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Quickstart VM
|
||||||
|
|
||||||
|
Default user: `ducoterra`
|
||||||
|
Default password: `osbuild`
|
||||||
|
|
||||||
|
- [ ] `passwd ducoterra`
|
||||||
|
- [ ] `hostnamectl hostname <hostname>`
|
||||||
|
- [ ] Updates
|
||||||
|
- [ ] Static IP and DNS address
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Convert the build to raw
|
||||||
|
qemu-img convert -f qcow2 -O raw \
|
||||||
|
/srv/smb/pool0/ducoterra/images/builds/fedora-43-base.qcow2 \
|
||||||
|
/srv/vm/pool1/fedora-boot.raw
|
||||||
|
|
||||||
|
# Install (Change password for default user ducoterra!)
|
||||||
|
virt-install \
|
||||||
|
--boot uefi,firmware.feature0.name=secure-boot,firmware.feature0.enabled=no \
|
||||||
|
--cpu host-passthrough --vcpus sockets=1,cores=8,threads=2 \
|
||||||
|
--ram=8192 \
|
||||||
|
--os-variant=fedora41 \
|
||||||
|
--network bridge:bridge0 \
|
||||||
|
--graphics none \
|
||||||
|
--console pty,target.type=virtio \
|
||||||
|
--name "fedora" \
|
||||||
|
--import --disk "path=/srv/vm/pool1/fedora-boot.raw,bus=virtio"
|
||||||
|
```
|
||||||
|
|
||||||
## Disk Mounts
|
## Disk Mounts
|
||||||
|
|
||||||
1. All btrfs `subvolid=5` volumes should be mounted under `/btrfs`
|
1. All btrfs `subvolid=5` volumes should be mounted under `/btrfs`
|
||||||
@@ -110,7 +140,7 @@ virt-install \
|
|||||||
--graphics none \
|
--graphics none \
|
||||||
--console pty,target.type=virtio \
|
--console pty,target.type=virtio \
|
||||||
--name "fedora" \
|
--name "fedora" \
|
||||||
--import --disk "path=/srv/vm/pool1/fedora-boot.raw,bus=virtio" \
|
--import --disk "path=/srv/vm/pool1/fedora-boot.raw,bus=virtio"
|
||||||
|
|
||||||
# If you need to pass through a PCIe card
|
# If you need to pass through a PCIe card
|
||||||
--hostdev pci_0000_4e_00_0 \
|
--hostdev pci_0000_4e_00_0 \
|
||||||
|
|||||||
BIN
active/device_esphome/images/va_idle.png
Normal file
BIN
active/device_esphome/images/va_idle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 277 KiB |
BIN
active/device_esphome/images/va_idle.xcf
Normal file
BIN
active/device_esphome/images/va_idle.xcf
Normal file
Binary file not shown.
BIN
active/device_esphome/images/va_listen.png
Normal file
BIN
active/device_esphome/images/va_listen.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 431 KiB |
BIN
active/device_esphome/images/va_listen.xcf
Normal file
BIN
active/device_esphome/images/va_listen.xcf
Normal file
Binary file not shown.
BIN
active/device_esphome/images/va_speak.png
Normal file
BIN
active/device_esphome/images/va_speak.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 430 KiB |
BIN
active/device_esphome/images/va_speak.xcf
Normal file
BIN
active/device_esphome/images/va_speak.xcf
Normal file
Binary file not shown.
@@ -43,6 +43,17 @@ ota:
|
|||||||
wifi:
|
wifi:
|
||||||
ssid: !secret wifi_ssid
|
ssid: !secret wifi_ssid
|
||||||
password: !secret wifi_password
|
password: !secret wifi_password
|
||||||
|
on_connect:
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: lbl_status
|
||||||
|
text: "IDLE"
|
||||||
|
- select.set:
|
||||||
|
id: dac_output
|
||||||
|
option: "LINE1"
|
||||||
|
on_disconnect:
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: lbl_status
|
||||||
|
text: "DISCONNECTED"
|
||||||
|
|
||||||
i2c:
|
i2c:
|
||||||
- id: bsp_bus
|
- id: bsp_bus
|
||||||
@@ -103,6 +114,7 @@ switch:
|
|||||||
pin:
|
pin:
|
||||||
pi4ioe5v6408: pi4ioe2
|
pi4ioe5v6408: pi4ioe2
|
||||||
number: 7
|
number: 7
|
||||||
|
restore_mode: ALWAYS_ON
|
||||||
- platform: gpio
|
- platform: gpio
|
||||||
id: wifi_antenna_int_ext
|
id: wifi_antenna_int_ext
|
||||||
pin:
|
pin:
|
||||||
@@ -159,8 +171,8 @@ sensor:
|
|||||||
lambda: |-
|
lambda: |-
|
||||||
float voltage = id(battery_voltage).state;
|
float voltage = id(battery_voltage).state;
|
||||||
// Adjust these values based on your battery's actual min/max voltage
|
// Adjust these values based on your battery's actual min/max voltage
|
||||||
float min_voltage = 6.0; // Discharged voltage
|
float min_voltage = 6.75; // Discharged voltage
|
||||||
float max_voltage = 8.23; // Fully charged voltage
|
float max_voltage = 8.2; // Fully charged voltage
|
||||||
float percentage = (voltage - min_voltage) / (max_voltage - min_voltage) * 100.0;
|
float percentage = (voltage - min_voltage) / (max_voltage - min_voltage) * 100.0;
|
||||||
if (percentage > 100.0) return 100.0;
|
if (percentage > 100.0) return 100.0;
|
||||||
if (percentage < 0.0) return 0.0;
|
if (percentage < 0.0) return 0.0;
|
||||||
@@ -168,6 +180,14 @@ sensor:
|
|||||||
update_interval: 60s
|
update_interval: 60s
|
||||||
unit_of_measurement: "%"
|
unit_of_measurement: "%"
|
||||||
accuracy_decimals: 1
|
accuracy_decimals: 1
|
||||||
|
id: battery_percent
|
||||||
|
on_value:
|
||||||
|
then:
|
||||||
|
- lvgl.label.update:
|
||||||
|
id: lbl_battery
|
||||||
|
text:
|
||||||
|
format: "Battery: %.1f%"
|
||||||
|
args: ["id(battery_percent).state"]
|
||||||
|
|
||||||
touchscreen:
|
touchscreen:
|
||||||
- platform: st7123
|
- platform: st7123
|
||||||
@@ -221,16 +241,26 @@ light:
|
|||||||
default_transition_length: 250ms
|
default_transition_length: 250ms
|
||||||
initial_state:
|
initial_state:
|
||||||
brightness: "100%"
|
brightness: "100%"
|
||||||
- platform: lvgl
|
|
||||||
widget: listen_led_widget
|
image:
|
||||||
name: LVGL light
|
defaults:
|
||||||
id: listen_led
|
type: rgb565
|
||||||
|
transparency: alpha_channel
|
||||||
|
resize: 512x512
|
||||||
|
byte_order: little_endian
|
||||||
|
images:
|
||||||
|
- file: "images/va_idle.png"
|
||||||
|
id: va_idle
|
||||||
|
- file: "images/va_listen.png"
|
||||||
|
id: va_listen
|
||||||
|
- file: "images/va_speak.png"
|
||||||
|
id: va_speak
|
||||||
|
|
||||||
lvgl:
|
lvgl:
|
||||||
byte_order: little_endian
|
byte_order: little_endian
|
||||||
|
|
||||||
on_idle:
|
on_idle:
|
||||||
timeout: 60s
|
timeout: 120s
|
||||||
then:
|
then:
|
||||||
- logger.log: "LVGL is idle"
|
- logger.log: "LVGL is idle"
|
||||||
- light.turn_off:
|
- light.turn_off:
|
||||||
@@ -238,31 +268,35 @@ lvgl:
|
|||||||
transition_length: 15s
|
transition_length: 15s
|
||||||
- lvgl.pause:
|
- lvgl.pause:
|
||||||
widgets:
|
widgets:
|
||||||
- led:
|
- image:
|
||||||
id: listen_led_widget
|
id: listen_icon_widget
|
||||||
|
src: va_idle
|
||||||
align: CENTER
|
align: CENTER
|
||||||
color: 0xFF0000
|
|
||||||
brightness: 0%
|
|
||||||
height: 100px
|
|
||||||
width: 100px
|
|
||||||
- label:
|
- label:
|
||||||
align: TOP_MID
|
align: TOP_MID
|
||||||
id: lbl_status
|
id: lbl_status
|
||||||
text_font: montserrat_48
|
text_font: montserrat_48
|
||||||
text: "IDLE"
|
text: "CONNECTING..."
|
||||||
- label:
|
- label:
|
||||||
align: BOTTOM_LEFT
|
align: BOTTOM_LEFT
|
||||||
id: lbl_version
|
id: lbl_version
|
||||||
text_font: montserrat_12
|
text_font: montserrat_12
|
||||||
text: "v0.1"
|
text: "v0.5"
|
||||||
|
- label:
|
||||||
|
align: BOTTOM_RIGHT
|
||||||
|
id: lbl_battery
|
||||||
|
text_font: montserrat_28
|
||||||
|
text: Loading...
|
||||||
|
|
||||||
# The DAC Output select needs to be manually (or with an automation) changed to `LINE1` for the onboard speaker
|
# The DAC Output select needs to be manually (or with an automation) changed to `LINE1` for the onboard speaker
|
||||||
select:
|
select:
|
||||||
- platform: es8388
|
- platform: es8388
|
||||||
dac_output:
|
dac_output:
|
||||||
name: DAC Output
|
name: DAC Output
|
||||||
|
id: dac_output
|
||||||
adc_input_mic:
|
adc_input_mic:
|
||||||
name: ADC Input Mic
|
name: ADC Input Mic
|
||||||
|
id: adc_input
|
||||||
|
|
||||||
- platform: template
|
- platform: template
|
||||||
id: wifi_antenna_select
|
id: wifi_antenna_select
|
||||||
@@ -339,44 +373,29 @@ voice_assistant:
|
|||||||
media_player: tab5_media_player
|
media_player: tab5_media_player
|
||||||
micro_wake_word: mww
|
micro_wake_word: mww
|
||||||
on_listening:
|
on_listening:
|
||||||
- if:
|
|
||||||
condition: lvgl.is_paused
|
|
||||||
then:
|
|
||||||
- logger.log: "LVGL resuming"
|
- logger.log: "LVGL resuming"
|
||||||
- lvgl.resume:
|
- lvgl.resume:
|
||||||
- light.turn_on: backlight
|
- light.turn_on: backlight
|
||||||
- light.turn_on:
|
- lvgl.image.update:
|
||||||
id: listen_led
|
id: listen_icon_widget
|
||||||
brightness: 100%
|
src: va_listen
|
||||||
red: 0%
|
|
||||||
green: 100%
|
|
||||||
blue: 0%
|
|
||||||
effect: "None`"
|
|
||||||
- lvgl.label.update:
|
- lvgl.label.update:
|
||||||
id: lbl_status
|
id: lbl_status
|
||||||
text: "LISTENING"
|
text: "LISTENING"
|
||||||
on_stt_vad_end:
|
on_stt_vad_end:
|
||||||
- light.turn_on:
|
|
||||||
id: listen_led
|
|
||||||
brightness: 100%
|
|
||||||
red: 0%
|
|
||||||
green: 0%
|
|
||||||
blue: 100%
|
|
||||||
effect: "None"
|
|
||||||
- lvgl.label.update:
|
- lvgl.label.update:
|
||||||
id: lbl_status
|
id: lbl_status
|
||||||
text: "PROCESSING"
|
text: "PROCESSING"
|
||||||
|
- lvgl.image.update:
|
||||||
|
id: listen_icon_widget
|
||||||
|
src: va_idle
|
||||||
on_tts_start:
|
on_tts_start:
|
||||||
- light.turn_on:
|
|
||||||
id: listen_led
|
|
||||||
brightness: 100%
|
|
||||||
red: 100%
|
|
||||||
green: 0%
|
|
||||||
blue: 0%
|
|
||||||
effect: "None"
|
|
||||||
- lvgl.label.update:
|
- lvgl.label.update:
|
||||||
id: lbl_status
|
id: lbl_status
|
||||||
text: "RESPONDING"
|
text: "RESPONDING"
|
||||||
|
- lvgl.image.update:
|
||||||
|
id: listen_icon_widget
|
||||||
|
src: va_speak
|
||||||
on_end:
|
on_end:
|
||||||
# Wait a short amount of time to see if an announcement starts
|
# Wait a short amount of time to see if an announcement starts
|
||||||
- wait_until:
|
- wait_until:
|
||||||
@@ -391,13 +410,12 @@ voice_assistant:
|
|||||||
- not:
|
- not:
|
||||||
speaker.is_playing:
|
speaker.is_playing:
|
||||||
- micro_wake_word.start:
|
- micro_wake_word.start:
|
||||||
- lvgl.led.update:
|
|
||||||
id: listen_led_widget
|
|
||||||
color: 0x000000
|
|
||||||
brightness: "0%"
|
|
||||||
- lvgl.label.update:
|
- lvgl.label.update:
|
||||||
id: lbl_status
|
id: lbl_status
|
||||||
text: "IDLE"
|
text: "IDLE"
|
||||||
|
- lvgl.image.update:
|
||||||
|
id: listen_icon_widget
|
||||||
|
src: va_idle
|
||||||
- light.turn_off:
|
- light.turn_off:
|
||||||
id: backlight
|
id: backlight
|
||||||
transition_length: 15s
|
transition_length: 15s
|
||||||
|
|||||||
1
active/device_keychron/Keymap-Q8 KNOB-11-10-44.json
Normal file
1
active/device_keychron/Keymap-Q8 KNOB-11-10-44.json
Normal file
File diff suppressed because one or more lines are too long
14
active/device_keychron/README.md
Normal file
14
active/device_keychron/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Keychron
|
||||||
|
|
||||||
|
## VIA
|
||||||
|
|
||||||
|
<`https://launcher.keychron.com/#/keymap`>
|
||||||
|
|
||||||
|
On linux with chromium you'll sometimes see "failed to connect" errors. This can
|
||||||
|
be resolved with `chmod a+rw /dev/hidrawX` where `X` is the id of the keyboard.
|
||||||
|
|
||||||
|
## Q8 Alice
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|

|
||||||
BIN
active/device_keychron/q8_L1.png
Normal file
BIN
active/device_keychron/q8_L1.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 231 KiB |
BIN
active/device_keychron/q8_L2.png
Normal file
BIN
active/device_keychron/q8_L2.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
@@ -1,7 +1,11 @@
|
|||||||
[Pod]
|
[Pod]
|
||||||
Network=ai-internal.network
|
Network=ai-internal.network
|
||||||
# llama.cpp
|
# llama.cpp server
|
||||||
PublishPort=8000:8000/tcp
|
PublishPort=8000:8000/tcp
|
||||||
|
# llama.cpp embed
|
||||||
|
PublishPort=8001:8001/tcp
|
||||||
|
# llama.cpp instruct
|
||||||
|
PublishPort=8002:8002/tcp
|
||||||
# stable-diffusion.cpp gen
|
# stable-diffusion.cpp gen
|
||||||
PublishPort=1234:1234/tcp
|
PublishPort=1234:1234/tcp
|
||||||
# stable-diffusion.cpp edit
|
# stable-diffusion.cpp edit
|
||||||
@@ -3,6 +3,10 @@
|
|||||||
- [Self Hosted AI Stack](#self-hosted-ai-stack)
|
- [Self Hosted AI Stack](#self-hosted-ai-stack)
|
||||||
- [Notes](#notes)
|
- [Notes](#notes)
|
||||||
- [Podman Volume Locations](#podman-volume-locations)
|
- [Podman Volume Locations](#podman-volume-locations)
|
||||||
|
- [List of Internal Links](#list-of-internal-links)
|
||||||
|
- [Quick Install](#quick-install)
|
||||||
|
- [Text Stack](#text-stack)
|
||||||
|
- [Image Stack](#image-stack)
|
||||||
- [Setup](#setup)
|
- [Setup](#setup)
|
||||||
- [Create the AI user](#create-the-ai-user)
|
- [Create the AI user](#create-the-ai-user)
|
||||||
- [Helper aliases](#helper-aliases)
|
- [Helper aliases](#helper-aliases)
|
||||||
@@ -17,6 +21,8 @@
|
|||||||
- [GLM](#glm)
|
- [GLM](#glm)
|
||||||
- [Gemma](#gemma)
|
- [Gemma](#gemma)
|
||||||
- [Dolphin](#dolphin)
|
- [Dolphin](#dolphin)
|
||||||
|
- [LiquidAI](#liquidai)
|
||||||
|
- [Level 1 Techs](#level-1-techs)
|
||||||
- [Image models](#image-models)
|
- [Image models](#image-models)
|
||||||
- [Z-Image](#z-image)
|
- [Z-Image](#z-image)
|
||||||
- [Flux](#flux)
|
- [Flux](#flux)
|
||||||
@@ -26,22 +32,71 @@
|
|||||||
- [llama.cpp](#llamacpp)
|
- [llama.cpp](#llamacpp)
|
||||||
- [stable-diffusion.cpp](#stable-diffusioncpp)
|
- [stable-diffusion.cpp](#stable-diffusioncpp)
|
||||||
- [open-webui](#open-webui)
|
- [open-webui](#open-webui)
|
||||||
|
- [lite-llm](#lite-llm)
|
||||||
- [Install Services with Quadlets](#install-services-with-quadlets)
|
- [Install Services with Quadlets](#install-services-with-quadlets)
|
||||||
- [Internal and External Pods](#internal-and-external-pods)
|
- [Internal and External Pods](#internal-and-external-pods)
|
||||||
- [Llama CPP Server](#llama-cpp-server)
|
- [Llama CPP Server (Port 8000)](#llama-cpp-server-port-8000)
|
||||||
- [Llama CPP Embedding Server](#llama-cpp-embedding-server)
|
- [Llama CPP Embedding Server (Port 8001)](#llama-cpp-embedding-server-port-8001)
|
||||||
- [Stable Diffusion CPP](#stable-diffusion-cpp)
|
- [Llama CPP Instruct Server (Port 8002)](#llama-cpp-instruct-server-port-8002)
|
||||||
- [Open Webui](#open-webui-1)
|
- [Stable Diffusion CPP (Port 1234 and 1235)](#stable-diffusion-cpp-port-1234-and-1235)
|
||||||
|
- [Open Webui (Port 8080)](#open-webui-port-8080)
|
||||||
- [Install the update script](#install-the-update-script)
|
- [Install the update script](#install-the-update-script)
|
||||||
- [Install Guest Open Webui with Start/Stop Services](#install-guest-open-webui-with-startstop-services)
|
- [Install Guest Open Webui with Start/Stop Services](#install-guest-open-webui-with-startstop-services)
|
||||||
- [Benchmark Results](#benchmark-results)
|
- [Benchmark Results](#benchmark-results)
|
||||||
|
- [Testing with Curl](#testing-with-curl)
|
||||||
|
- [OpenAI API](#openai-api)
|
||||||
|
- [Misc](#misc)
|
||||||
|
- [Qwen3.5 Settings](#qwen35-settings)
|
||||||
|
|
||||||
## Notes
|
## Notes
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Shortcut for downloading models
|
||||||
|
hf-download ()
|
||||||
|
{
|
||||||
|
if [ $# -ne 3 ]; then
|
||||||
|
echo "ERROR: Expected 3 arguments, but only got $#" 1>&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
BASE_DIR='/opt/ai/models'
|
||||||
|
mkdir -p $BASE_DIR/$1
|
||||||
|
pushd $BASE_DIR/$1 2>&1 >/dev/null
|
||||||
|
hf download --local-dir . $2 $3
|
||||||
|
popd 2>&1 >/dev/null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Podman Volume Locations
|
### Podman Volume Locations
|
||||||
|
|
||||||
`~/.local/share/containers/storage/volumes/`
|
`~/.local/share/containers/storage/volumes/`
|
||||||
|
|
||||||
|
### List of Internal Links
|
||||||
|
|
||||||
|
- llama-cpp
|
||||||
|
- llama-embed
|
||||||
|
- llama-instruct
|
||||||
|
- image-gen
|
||||||
|
- image-edit
|
||||||
|
- openwebui
|
||||||
|
|
||||||
|
## Quick Install
|
||||||
|
|
||||||
|
### Text Stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook \
|
||||||
|
-i ansible/inventory.yaml \
|
||||||
|
active/software_ai_stack/install_ai_text_stack.yaml
|
||||||
|
```
|
||||||
|
|
||||||
|
### Image Stack
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ansible-playbook \
|
||||||
|
-i ansible/inventory.yaml \
|
||||||
|
active/software_ai_stack/install_ai_image_stack.yaml
|
||||||
|
```
|
||||||
|
|
||||||
## Setup
|
## Setup
|
||||||
|
|
||||||
### Create the AI user
|
### Create the AI user
|
||||||
@@ -160,9 +215,15 @@ hf download --local-dir . ggml-org/Ministral-3-3B-Instruct-2512-GGUF
|
|||||||
##### Qwen
|
##### Qwen
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# qwen3-30b-a3b-thinking
|
# qwen3.5-4b
|
||||||
mkdir qwen3-30b-a3b-thinking && cd qwen3-30b-a3b-thinking
|
mkdir qwen3.5-4b && cd qwen3.5-4b
|
||||||
hf download --local-dir . ggml-org/Qwen3-30B-A3B-Thinking-2507-Q8_0-GGUF
|
hf download --local-dir . unsloth/Qwen3.5-4B-GGUF Qwen3.5-4B-Q8_0.gguf
|
||||||
|
hf download --local-dir . unsloth/Qwen3.5-4B-GGUF mmproj-F16.gguf
|
||||||
|
|
||||||
|
# qwen3.5-35b-a3b
|
||||||
|
mkdir qwen3.5-35b-a3b && cd qwen3.5-35b-a3b
|
||||||
|
hf download --local-dir . unsloth/Qwen3.5-35B-A3B-GGUF Qwen3.5-35B-A3B-Q8_0.gguf
|
||||||
|
hf download --local-dir . unsloth/Qwen3.5-35B-A3B-GGUF mmproj-F16.gguf
|
||||||
|
|
||||||
# qwen3-30b-a3b-instruct
|
# qwen3-30b-a3b-instruct
|
||||||
mkdir qwen3-30b-a3b-instruct && cd qwen3-30b-a3b-instruct
|
mkdir qwen3-30b-a3b-instruct && cd qwen3-30b-a3b-instruct
|
||||||
@@ -185,6 +246,10 @@ hf download --local-dir . ggml-org/Qwen3-Coder-30B-A3B-Instruct-Q8_0-GGUF
|
|||||||
# qwen3-coder-next
|
# qwen3-coder-next
|
||||||
mkdir qwen3-coder-next && cd qwen3-coder-next
|
mkdir qwen3-coder-next && cd qwen3-coder-next
|
||||||
hf download --local-dir . unsloth/Qwen3-Coder-Next-GGUF --include "Q8_0/*.gguf"
|
hf download --local-dir . unsloth/Qwen3-Coder-Next-GGUF --include "Q8_0/*.gguf"
|
||||||
|
|
||||||
|
# qwen3-8b (benchmarks)
|
||||||
|
mkdir qwen3-8b && cd qwen3-8b
|
||||||
|
hf download --local-dir . Qwen/Qwen3-8B-GGUF Qwen3-8B-Q8_0.gguf
|
||||||
```
|
```
|
||||||
|
|
||||||
##### GLM
|
##### GLM
|
||||||
@@ -210,10 +275,26 @@ hf download --local-dir . unsloth/gemma-3-27b-it-GGUF mmproj-F16.gguf
|
|||||||
```bash
|
```bash
|
||||||
# dolphin-mistral-24b-venice
|
# dolphin-mistral-24b-venice
|
||||||
mkdir dolphin-mistral-24b-venice && cd dolphin-mistral-24b-venice
|
mkdir dolphin-mistral-24b-venice && cd dolphin-mistral-24b-venice
|
||||||
cd dolphin-mistral-24b-venice
|
|
||||||
hf download --local-dir . bartowski/cognitivecomputations_Dolphin-Mistral-24B-Venice-Edition-GGUF cognitivecomputations_Dolphin-Mistral-24B-Venice-Edition-Q8_0.gguf
|
hf download --local-dir . bartowski/cognitivecomputations_Dolphin-Mistral-24B-Venice-Edition-GGUF cognitivecomputations_Dolphin-Mistral-24B-Venice-Edition-Q8_0.gguf
|
||||||
```
|
```
|
||||||
|
|
||||||
|
##### LiquidAI
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# lfm2-24b
|
||||||
|
mkdir lfm2-24b && cd lfm2-24b
|
||||||
|
hf download --local-dir . LiquidAI/LFM2-24B-A2B-GGUF LFM2-24B-A2B-Q8_0.gguf
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Level 1 Techs
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# kappa-20b
|
||||||
|
# https://huggingface.co/eousphoros/kappa-20b-131k-GGUF-Q8_0/tree/main
|
||||||
|
mkdir kappa-20b && cd kappa-20b
|
||||||
|
hf download --local-dir . eousphoros/kappa-20b-131k-GGUF-Q8_0
|
||||||
|
```
|
||||||
|
|
||||||
#### Image models
|
#### Image models
|
||||||
|
|
||||||
##### Z-Image
|
##### Z-Image
|
||||||
@@ -244,8 +325,8 @@ hf download --local-dir . unsloth/Qwen3-8B-GGUF Qwen3-8B-Q8_0.gguf
|
|||||||
##### Qwen Embedding
|
##### Qwen Embedding
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir /home/ai/models/embedding/qwen3-vl-embed && cd /home/ai/models/embedding/qwen3-vl-embed
|
mkdir qwen3-embed-4b && cd qwen3-embed-4b
|
||||||
hf download --local-dir . dam2452/Qwen3-VL-Embedding-8B-GGUF Qwen3-VL-Embedding-8B-Q8_0.gguf
|
hf download --local-dir . Qwen/Qwen3-Embedding-4B-GGUF Qwen3-Embedding-4B-Q8_0.gguf
|
||||||
```
|
```
|
||||||
|
|
||||||
##### Nomic Embedding
|
##### Nomic Embedding
|
||||||
@@ -279,16 +360,44 @@ podman run \
|
|||||||
--device=/dev/kfd \
|
--device=/dev/kfd \
|
||||||
--device=/dev/dri \
|
--device=/dev/dri \
|
||||||
-v /home/ai/models/text:/models:z \
|
-v /home/ai/models/text:/models:z \
|
||||||
-p 8000:8000 \
|
-p 8010:8000 \
|
||||||
localhost/llama-cpp-vulkan:latest \
|
localhost/llama-cpp-vulkan:latest \
|
||||||
--host 0.0.0.0 \
|
--host 0.0.0.0 \
|
||||||
--port 8000 \
|
--port 8000 \
|
||||||
-c 32768 \
|
-c 16000 \
|
||||||
--perf \
|
--perf \
|
||||||
--n-gpu-layers all \
|
--n-gpu-layers all \
|
||||||
--jinja \
|
--jinja \
|
||||||
--models-max 1 \
|
--models-max 1 \
|
||||||
--models-dir /models
|
--models-dir /models \
|
||||||
|
--chat-template-kwargs '{"enable_thinking": false}' \
|
||||||
|
-m /models/qwen3.5-35b-a3b
|
||||||
|
```
|
||||||
|
|
||||||
|
Embedding models
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman run \
|
||||||
|
--rm \
|
||||||
|
--name llama-server-demo \
|
||||||
|
--device=/dev/kfd \
|
||||||
|
--device=/dev/dri \
|
||||||
|
-v /home/ai/models/text:/models:z \
|
||||||
|
-p 8000:8000 \
|
||||||
|
localhost/llama-cpp-vulkan:latest \
|
||||||
|
--host 0.0.0.0 \
|
||||||
|
--port 8001 \
|
||||||
|
-c 512 \
|
||||||
|
--perf \
|
||||||
|
--n-gpu-layers all \
|
||||||
|
--models-max 1 \
|
||||||
|
--models-dir /models \
|
||||||
|
--embedding
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Test with curl
|
||||||
|
curl -X POST "https://llama-embed.reeselink.com/embedding" --data '{"model": "qwen3-embed-4b", "content":"Star Wars is better than Star Trek"}'
|
||||||
```
|
```
|
||||||
|
|
||||||
## stable-diffusion.cpp
|
## stable-diffusion.cpp
|
||||||
@@ -354,6 +463,37 @@ localhost/stable-diffusion-cpp:latest \
|
|||||||
-r /output/output.png \
|
-r /output/output.png \
|
||||||
-o /output/edit.png \
|
-o /output/edit.png \
|
||||||
-p "Replace the dragon with an old car"
|
-p "Replace the dragon with an old car"
|
||||||
|
|
||||||
|
# Video generation with wan2.2
|
||||||
|
podman run --rm \
|
||||||
|
-v /home/ai/models:/models:z \
|
||||||
|
-v /home/ai/output:/output:z \
|
||||||
|
--device /dev/kfd \
|
||||||
|
--device /dev/dri \
|
||||||
|
localhost/stable-diffusion-cpp:latest \
|
||||||
|
-M vid_gen \
|
||||||
|
--diffusion-model /models/video/wan2.2/Wan2.2-T2V-A14B-LowNoise-Q5_K_M.gguf \
|
||||||
|
--high-noise-diffusion-model /models/video/wan2.2/Wan2.2-T2V-A14B-HighNoise-Q5_K_M.gguf \
|
||||||
|
--vae /models/video/wan2.2/wan_2.1_vae.safetensors \
|
||||||
|
--t5xxl /models/video/wan2.2/umt5-xxl-encoder-Q5_K_M.gguf \
|
||||||
|
--cfg-scale 3.5 \
|
||||||
|
--sampling-method euler \
|
||||||
|
--steps 10 \
|
||||||
|
--high-noise-cfg-scale 3.5 \
|
||||||
|
--high-noise-sampling-method euler \
|
||||||
|
--high-noise-steps 8 \
|
||||||
|
--vae-conv-direct \
|
||||||
|
--diffusion-conv-direct \
|
||||||
|
--vae-tiling \
|
||||||
|
-v \
|
||||||
|
-n "Colorful tones, overexposed, static, blurred details, subtitles, style, artwork, painting, picture, still, overall graying, worst quality, low quality, JPEG compression residue, ugly, mutilated, extra fingers, poorly drawn hands, poorly drawn faces, deformed, disfigured, deformed limbs, finger fusion, still pictures, messy backgrounds, three legs, many people in the background, walking backwards" \
|
||||||
|
-W 512 \
|
||||||
|
-H 512 \
|
||||||
|
--diffusion-fa \
|
||||||
|
--video-frames 24 \
|
||||||
|
--flow-shift 3.0 \
|
||||||
|
-o /output/video_output \
|
||||||
|
-p "A normal business meeting. People discuss business for 2 seconds. Suddenly, a horde of furries carrying assault rifles bursts into the room and causes a panic. Hatsune Miku leads the charge screaming in rage."
|
||||||
```
|
```
|
||||||
|
|
||||||
## open-webui
|
## open-webui
|
||||||
@@ -382,6 +522,17 @@ Use the following connections:
|
|||||||
| stable-diffusion.cpp | <http://host.containers.internal:1234/v1> |
|
| stable-diffusion.cpp | <http://host.containers.internal:1234/v1> |
|
||||||
| stable-diffusion.cpp edit | <http://host.containers.internal:1235/v1> |
|
| stable-diffusion.cpp edit | <http://host.containers.internal:1235/v1> |
|
||||||
|
|
||||||
|
## lite-llm
|
||||||
|
|
||||||
|
<https://docs.litellm.ai/docs/proxy/configs>
|
||||||
|
|
||||||
|
```bash
|
||||||
|
podman run \
|
||||||
|
--rm \
|
||||||
|
--name litellm \
|
||||||
|
-p 4000:4000
|
||||||
|
```
|
||||||
|
|
||||||
## Install Services with Quadlets
|
## Install Services with Quadlets
|
||||||
|
|
||||||
### Internal and External Pods
|
### Internal and External Pods
|
||||||
@@ -397,18 +548,18 @@ systemctl --user daemon-reload
|
|||||||
systemctl --user start ai-internal-pod.service ai-external-pod.service
|
systemctl --user start ai-internal-pod.service ai-external-pod.service
|
||||||
```
|
```
|
||||||
|
|
||||||
### Llama CPP Server
|
### Llama CPP Server (Port 8000)
|
||||||
|
|
||||||
Installs the llama.cpp server to run our text models.
|
Installs the llama.cpp server to run our text models.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
scp -r active/software_ai_stack/quadlets_llama_server/* deskwork-ai:.config/containers/systemd/
|
scp -r active/software_ai_stack/quadlets_llama_think/* deskwork-ai:.config/containers/systemd/
|
||||||
ssh deskwork-ai
|
ssh deskwork-ai
|
||||||
systemctl --user daemon-reload
|
systemctl --user daemon-reload
|
||||||
systemctl --user restart ai-internal-pod.service
|
systemctl --user restart ai-internal-pod.service
|
||||||
```
|
```
|
||||||
|
|
||||||
### Llama CPP Embedding Server
|
### Llama CPP Embedding Server (Port 8001)
|
||||||
|
|
||||||
Installs the llama.cpp server to run our embedding models
|
Installs the llama.cpp server to run our embedding models
|
||||||
|
|
||||||
@@ -419,7 +570,18 @@ systemctl --user daemon-reload
|
|||||||
systemctl --user restart ai-internal-pod.service
|
systemctl --user restart ai-internal-pod.service
|
||||||
```
|
```
|
||||||
|
|
||||||
### Stable Diffusion CPP
|
### Llama CPP Instruct Server (Port 8002)
|
||||||
|
|
||||||
|
Installs the llama.cpp server to run a constant instruct (no thinking) model for quick replies
|
||||||
|
|
||||||
|
```bash
|
||||||
|
scp -r active/software_ai_stack/quadlets_llama_instruct/* deskwork-ai:.config/containers/systemd/
|
||||||
|
ssh deskwork-ai
|
||||||
|
systemctl --user daemon-reload
|
||||||
|
systemctl --user restart ai-internal-pod.service
|
||||||
|
```
|
||||||
|
|
||||||
|
### Stable Diffusion CPP (Port 1234 and 1235)
|
||||||
|
|
||||||
Installs the stable-diffusion.cpp server to run our image models.
|
Installs the stable-diffusion.cpp server to run our image models.
|
||||||
|
|
||||||
@@ -430,7 +592,7 @@ systemctl --user daemon-reload
|
|||||||
systemctl --user restart ai-internal-pod.service
|
systemctl --user restart ai-internal-pod.service
|
||||||
```
|
```
|
||||||
|
|
||||||
### Open Webui
|
### Open Webui (Port 8080)
|
||||||
|
|
||||||
Installs the open webui frontend.
|
Installs the open webui frontend.
|
||||||
|
|
||||||
@@ -482,27 +644,22 @@ podman run -it --rm \
|
|||||||
ghcr.io/ggml-org/llama.cpp:full-vulkan
|
ghcr.io/ggml-org/llama.cpp:full-vulkan
|
||||||
|
|
||||||
# Benchmark command
|
# Benchmark command
|
||||||
./llama-bench -m /models/benchmark/gpt-oss-20b-Q8_0.gguf
|
./llama-bench -m /models/gpt-oss-20b/gpt-oss-20b-Q8_0.gguf -p 4096 -n 1024
|
||||||
```
|
```
|
||||||
|
|
||||||
Framework Desktop
|
Framework Desktop
|
||||||
|
|
||||||
| model | size | params | backend | ngl | test | t/s |
|
| model | size | params | backend | ngl | test | t/s |
|
||||||
| ---------------- | --------: | ------: | ------- | ---: | ----: | -------------: |
|
| ---------------- | --------: | ------: | ------- | ---: | -----: | ------------: |
|
||||||
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | Vulkan | 99 | pp512 | 1128.50 ± 7.60 |
|
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | Vulkan | 99 | pp4096 | 992.74 ± 6.07 |
|
||||||
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | Vulkan | 99 | tg128 | 77.94 ± 0.08 |
|
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | Vulkan | 99 | tg1024 | 75.82 ± 0.07 |
|
||||||
|
|
||||||
| model | size | params | backend | ngl | test | t/s |
|
|
||||||
| ---------------- | --------: | ------: | ------- | ---: | ----: | ------------: |
|
|
||||||
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | ROCm | 99 | pp512 | 526.05 ± 7.04 |
|
|
||||||
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | ROCm | 99 | tg128 | 70.98 ± 0.01 |
|
|
||||||
|
|
||||||
AMD R9700
|
AMD R9700
|
||||||
|
|
||||||
| model | size | params | backend | ngl | test | t/s |
|
| model | size | params | backend | ngl | test | t/s |
|
||||||
| ---------------- | --------: | ------: | ------- | ---: | ----: | ---------------: |
|
| ---------------- | --------: | ------: | ------- | ---: | -----: | -------------: |
|
||||||
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | Vulkan | 99 | pp512 | 3756.79 ± 203.97 |
|
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | Vulkan | 99 | pp4096 | 3190.85 ± 8.24 |
|
||||||
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | Vulkan | 99 | tg128 | 174.24 ± 0.32 |
|
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | Vulkan | 99 | tg1024 | 168.73 ± 0.15 |
|
||||||
|
|
||||||
NVIDIA GeForce RTX 4080 SUPER
|
NVIDIA GeForce RTX 4080 SUPER
|
||||||
|
|
||||||
@@ -515,9 +672,9 @@ NVIDIA GeForce RTX 4080 SUPER
|
|||||||
NVIDIA GeForce RTX 3090
|
NVIDIA GeForce RTX 3090
|
||||||
|
|
||||||
| model | size | params | backend | ngl | test | t/s |
|
| model | size | params | backend | ngl | test | t/s |
|
||||||
| ---------------- | --------: | ------: | ------- | ---: | ----: | --------------: |
|
| ---------------- | --------: | ------: | ----------- | ---: | -----: | --------------: |
|
||||||
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | CUDA | 99 | pp512 | 4297.72 ± 35.60 |
|
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | CUDA,Vulkan | 99 | pp4096 | 3034.03 ± 80.36 |
|
||||||
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | CUDA | 99 | tg128 | 197.73 ± 0.62 |
|
| gpt-oss 20B Q8_0 | 11.27 GiB | 20.91 B | CUDA,Vulkan | 99 | tg1024 | 181.05 ± 9.01 |
|
||||||
|
|
||||||
Apple M4 max
|
Apple M4 max
|
||||||
|
|
||||||
@@ -525,3 +682,89 @@ Apple M4 max
|
|||||||
| :---------------------------- | -----: | -------------: |
|
| :---------------------------- | -----: | -------------: |
|
||||||
| unsloth/gpt-oss-20b-Q8_0-GGUF | pp2048 | 1579.12 ± 7.12 |
|
| unsloth/gpt-oss-20b-Q8_0-GGUF | pp2048 | 1579.12 ± 7.12 |
|
||||||
| unsloth/gpt-oss-20b-Q8_0-GGUF | tg32 | 113.00 ± 2.81 |
|
| unsloth/gpt-oss-20b-Q8_0-GGUF | tg32 | 113.00 ± 2.81 |
|
||||||
|
|
||||||
|
## Testing with Curl
|
||||||
|
|
||||||
|
### OpenAI API
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export TOKEN=$(cat active/software_ai_stack/secrets/aipi-token)
|
||||||
|
|
||||||
|
# List Models
|
||||||
|
curl https://aipi.reeseapps.com/v1/models \
|
||||||
|
-H "Authorization: Bearer $TOKEN" | jq
|
||||||
|
|
||||||
|
# Text
|
||||||
|
curl https://aipi.reeseapps.com/v1/chat/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"model": "llama-instruct/instruct",
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": "You are a helpful assistant."},
|
||||||
|
{"role": "user", "content": "Hello, how are you?"}
|
||||||
|
],
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 500
|
||||||
|
}' | jq
|
||||||
|
|
||||||
|
# Completion
|
||||||
|
curl https://aipi.reeseapps.com/v1/completions \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"model": "llama-instruct/instruct",
|
||||||
|
"prompt": "Write a short poem about the ocean.",
|
||||||
|
"temperature": 0.7,
|
||||||
|
"max_tokens": 500,
|
||||||
|
"top_p": 1,
|
||||||
|
"frequency_penalty": 0,
|
||||||
|
"presence_penalty": 0
|
||||||
|
}' | jq
|
||||||
|
|
||||||
|
# Image Gen
|
||||||
|
curl https://aipi.reeseapps.com/v1/images/generations \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"model": "sdd-gen/sd-cpp-local",
|
||||||
|
"prompt": "A futuristic city with flying cars at sunset, digital art",
|
||||||
|
"n": 1,
|
||||||
|
"size": "1024x1024"
|
||||||
|
}' | jq
|
||||||
|
|
||||||
|
# Image Edit
|
||||||
|
curl http://aipi.reeseapps.com/v1/images/edits \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-d '{
|
||||||
|
"model": "sdd-edit/sd-cpp-local",
|
||||||
|
"image": "@path/to/your/image.jpg",
|
||||||
|
"prompt": "Add a sunset background",
|
||||||
|
"n": 1,
|
||||||
|
"size": "1024x1024"
|
||||||
|
}'
|
||||||
|
|
||||||
|
# Embed
|
||||||
|
curl \
|
||||||
|
"https://aipi.reeseapps.com/v1/embeddings" \
|
||||||
|
-H "Authorization: Bearer $TOKEN" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"model": "llama-embed/embed",
|
||||||
|
"input":"This is the reason you ended up here:",
|
||||||
|
"encoding_format": "float"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
|
||||||
|
### Qwen3.5 Settings
|
||||||
|
|
||||||
|
> We recommend using the following set of sampling parameters for generation
|
||||||
|
|
||||||
|
- Non-thinking mode for text tasks: temperature=1.0, top_p=1.00, top_k=20, min_p=0.0, presence_penalty=2.0, repetition_penalty=1.0
|
||||||
|
- Non-thinking mode for VL tasks: temperature=0.7, top_p=0.80, top_k=20, min_p=0.0, presence_penalty=1.5, repetition_penalty=1.0
|
||||||
|
- Thinking mode for text tasks: temperature=1.0, top_p=0.95, top_k=20, min_p=0.0, presence_penalty=1.5, repetition_penalty=1.0
|
||||||
|
- Thinking mode for VL or precise coding (e.g. WebDev) tasks : temperature=0.6, top_p=0.95, top_k=20, min_p=0.0, presence_penalty=0.0, repetition_penalty=1.0
|
||||||
|
|
||||||
|
> Please note that the support for sampling parameters varies according to inference frameworks.
|
||||||
|
|||||||
23
active/software_ai_stack/install_ai_image_stack.yaml
Normal file
23
active/software_ai_stack/install_ai_image_stack.yaml
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
- name: Create Deskwork AI Stack
|
||||||
|
hosts: toybox-ai
|
||||||
|
tasks:
|
||||||
|
- name: Create /home/ai/.config/containers/systemd
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /home/ai/.config/containers/systemd
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
- name: Copy Quadlets
|
||||||
|
template:
|
||||||
|
src: "{{ item }}"
|
||||||
|
dest: "/home/ai/.config/containers/systemd/{{ item }}"
|
||||||
|
loop:
|
||||||
|
- ai-internal.network
|
||||||
|
- ai-internal.pod
|
||||||
|
- stable-diffusion-gen-server.container
|
||||||
|
- stable-diffusion-edit-server.container
|
||||||
|
- name: Reload and start the ai-internal-pod service
|
||||||
|
ansible.builtin.systemd_service:
|
||||||
|
state: restarted
|
||||||
|
name: ai-internal-pod.service
|
||||||
|
daemon_reload: true
|
||||||
|
scope: user
|
||||||
24
active/software_ai_stack/install_ai_text_stack.yaml
Normal file
24
active/software_ai_stack/install_ai_text_stack.yaml
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
- name: Create Deskwork AI Stack
|
||||||
|
hosts: deskwork-ai
|
||||||
|
tasks:
|
||||||
|
- name: Create /home/ai/.config/containers/systemd
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: /home/ai/.config/containers/systemd
|
||||||
|
state: directory
|
||||||
|
mode: "0755"
|
||||||
|
- name: Copy Quadlets
|
||||||
|
template:
|
||||||
|
src: "{{ item }}"
|
||||||
|
dest: "/home/ai/.config/containers/systemd/{{ item }}"
|
||||||
|
loop:
|
||||||
|
- ai-internal.network
|
||||||
|
- ai-internal.pod
|
||||||
|
- llama-embed.container
|
||||||
|
- llama-instruct.container
|
||||||
|
- llama-think.container
|
||||||
|
- name: Reload and start the ai-internal-pod service
|
||||||
|
ansible.builtin.systemd_service:
|
||||||
|
state: restarted
|
||||||
|
name: ai-internal-pod.service
|
||||||
|
daemon_reload: true
|
||||||
|
scope: user
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
[Unit]
|
[Unit]
|
||||||
Description=A Llama CPP Server running an Embedding Model
|
Description=A Llama CPP Server For Embedding Models
|
||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
# Shared AI internal pod
|
# Shared AI internal pod
|
||||||
@@ -17,9 +17,14 @@ AddDevice=/dev/dri
|
|||||||
|
|
||||||
# Server command
|
# Server command
|
||||||
Exec=--port 8001 \
|
Exec=--port 8001 \
|
||||||
|
-c 0 \
|
||||||
|
--perf \
|
||||||
--n-gpu-layers all \
|
--n-gpu-layers all \
|
||||||
--embeddings \
|
--models-max 1 \
|
||||||
-m /models/nomic-embed-text-v2/nomic-embed-text-v2-moe-q8_0.gguf
|
--models-dir /models \
|
||||||
|
--embedding \
|
||||||
|
-m /models/qwen3-embed-4b/Qwen3-Embedding-4B-Q8_0.gguf \
|
||||||
|
--alias embed
|
||||||
|
|
||||||
# Health Check
|
# Health Check
|
||||||
HealthCmd=CMD-SHELL curl --fail http://127.0.0.1:8001/props || exit 1
|
HealthCmd=CMD-SHELL curl --fail http://127.0.0.1:8001/props || exit 1
|
||||||
51
active/software_ai_stack/llama-instruct.container
Normal file
51
active/software_ai_stack/llama-instruct.container
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
[Unit]
|
||||||
|
Description=A Llama CPP Server Running GPT OSS 120b
|
||||||
|
|
||||||
|
[Container]
|
||||||
|
# Shared AI internal pod
|
||||||
|
Pod=ai-internal.pod
|
||||||
|
|
||||||
|
# Image is built locally via podman build
|
||||||
|
Image=localhost/llama-cpp-vulkan:latest
|
||||||
|
|
||||||
|
# Downloaded models volume
|
||||||
|
Volume=/home/ai/models/text:/models:z
|
||||||
|
|
||||||
|
# GPU Device
|
||||||
|
AddDevice=/dev/kfd
|
||||||
|
AddDevice=/dev/dri
|
||||||
|
|
||||||
|
# Server command
|
||||||
|
Exec=--port 8002 \
|
||||||
|
-c 16000 \
|
||||||
|
--perf \
|
||||||
|
-v \
|
||||||
|
--top-k 20 \
|
||||||
|
--top-p 0.8 \
|
||||||
|
--min-p 0 \
|
||||||
|
--presence-penalty 1.5 \
|
||||||
|
--repeat-penalty 1 \
|
||||||
|
--temp 0.7 \
|
||||||
|
--n-gpu-layers all \
|
||||||
|
--jinja \
|
||||||
|
--chat-template-kwargs '{"enable_thinking": false}' \
|
||||||
|
-m /models/qwen3.5-35b-a3b/Qwen3.5-35B-A3B-Q8_0.gguf \
|
||||||
|
--mmproj /models/qwen3.5-35b-a3b/mmproj-F16.gguf \
|
||||||
|
--alias instruct
|
||||||
|
|
||||||
|
# Health Check
|
||||||
|
HealthCmd=CMD-SHELL curl --fail http://127.0.0.1:8000/health || exit 1
|
||||||
|
HealthInterval=10s
|
||||||
|
HealthRetries=3
|
||||||
|
HealthStartPeriod=10s
|
||||||
|
HealthTimeout=30s
|
||||||
|
HealthOnFailure=kill
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Restart=always
|
||||||
|
# Extend Timeout to allow time to pull the image
|
||||||
|
TimeoutStartSec=900
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
# Start by default on boot
|
||||||
|
WantedBy=multi-user.target default.target
|
||||||
@@ -17,7 +17,7 @@ AddDevice=/dev/dri
|
|||||||
|
|
||||||
# Server command
|
# Server command
|
||||||
Exec=--port 8000 \
|
Exec=--port 8000 \
|
||||||
-c 16384 \
|
-c 64000 \
|
||||||
--perf \
|
--perf \
|
||||||
--n-gpu-layers all \
|
--n-gpu-layers all \
|
||||||
--jinja \
|
--jinja \
|
||||||
@@ -25,7 +25,7 @@ Exec=--port 8000 \
|
|||||||
--models-dir /models
|
--models-dir /models
|
||||||
|
|
||||||
# Health Check
|
# Health Check
|
||||||
HealthCmd=CMD-SHELL curl --fail http://127.0.0.1:8000/props || exit 1
|
HealthCmd=CMD-SHELL curl --fail http://127.0.0.1:8000/health || exit 1
|
||||||
HealthInterval=10s
|
HealthInterval=10s
|
||||||
HealthRetries=3
|
HealthRetries=3
|
||||||
HealthStartPeriod=10s
|
HealthStartPeriod=10s
|
||||||
@@ -3,7 +3,7 @@ Description=An Open Webui Frontend for Local AI Services
|
|||||||
|
|
||||||
[Container]
|
[Container]
|
||||||
# Shared AI external pod
|
# Shared AI external pod
|
||||||
Pod=ai-external.pod
|
PublishPort=8080:8080
|
||||||
|
|
||||||
# Open Webui base image
|
# Open Webui base image
|
||||||
Image=ghcr.io/open-webui/open-webui:main
|
Image=ghcr.io/open-webui/open-webui:main
|
||||||
133
active/software_ai_stack/openai-example.py
Normal file
133
active/software_ai_stack/openai-example.py
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
import base64
|
||||||
|
import os
|
||||||
|
from datetime import datetime
|
||||||
|
from io import BytesIO
|
||||||
|
|
||||||
|
import requests
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
|
# Configuration
|
||||||
|
BASE_URL = "https://llama-cpp.reeselink.com"
|
||||||
|
API_KEY = os.getenv("LLAMA_CPP_API_KEY", "") # Set if required
|
||||||
|
|
||||||
|
|
||||||
|
def call_api(endpoint, method="GET", data=None):
|
||||||
|
"""Generic API call helper"""
|
||||||
|
url = f"{BASE_URL}/v1/{endpoint}"
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if API_KEY:
|
||||||
|
headers["Authorization"] = f"Bearer {API_KEY}"
|
||||||
|
|
||||||
|
response = requests.request(method, url, headers=headers, json=data)
|
||||||
|
return response
|
||||||
|
|
||||||
|
|
||||||
|
# 1. List Models
|
||||||
|
models_response = call_api("models")
|
||||||
|
models = models_response.json().get("data", [])
|
||||||
|
print(f"Available models: {[m['id'] for m in models]}")
|
||||||
|
|
||||||
|
# 2. Use First Model
|
||||||
|
model_id = models[1]["id"]
|
||||||
|
|
||||||
|
# 3. Chat Completion
|
||||||
|
chat_data = {
|
||||||
|
"model": model_id,
|
||||||
|
"messages": [
|
||||||
|
{"role": "system", "content": "You are helpful."},
|
||||||
|
{"role": "user", "content": "Tell me about Everquest!"},
|
||||||
|
],
|
||||||
|
"temperature": 0.95,
|
||||||
|
"max_tokens": 100,
|
||||||
|
}
|
||||||
|
response = call_api("chat/completions", "POST", chat_data)
|
||||||
|
print(response.json()["choices"][0]["message"]["content"])
|
||||||
|
|
||||||
|
|
||||||
|
def describe_image(image_path, api_key=None):
|
||||||
|
"""
|
||||||
|
Send an image to the LLM for description
|
||||||
|
"""
|
||||||
|
base_url = "https://llama-cpp.reeselink.com"
|
||||||
|
|
||||||
|
# Read and encode image to base64
|
||||||
|
with open(image_path, "rb") as f:
|
||||||
|
encoded_image = base64.b64encode(f.read()).decode("utf-8")
|
||||||
|
|
||||||
|
# Prepare headers
|
||||||
|
headers = {"Content-Type": "application/json"}
|
||||||
|
if api_key:
|
||||||
|
headers["Authorization"] = f"Bearer {api_key}"
|
||||||
|
|
||||||
|
# Create payload
|
||||||
|
payload = {
|
||||||
|
"model": "qwen3-vl-30b-a3b-instruct", # 👁️ VISION MODEL
|
||||||
|
"messages": [
|
||||||
|
{
|
||||||
|
"role": "user",
|
||||||
|
"content": [
|
||||||
|
{"type": "text", "text": "Describe this image in detail"},
|
||||||
|
{
|
||||||
|
"type": "image_url",
|
||||||
|
"image_url": {"url": f"data:image/jpeg;base64,{encoded_image}"},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"max_tokens": 1000,
|
||||||
|
"temperature": 0.7,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Send request
|
||||||
|
response = requests.post(
|
||||||
|
f"{base_url}/v1/chat/completions", headers=headers, json=payload
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()["choices"][0]["message"]["content"]
|
||||||
|
else:
|
||||||
|
print(f"Error: {response.status_code}")
|
||||||
|
print(response.text)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# description = describe_image("generated-image.png", api_key="your_key")
|
||||||
|
# print(description)
|
||||||
|
|
||||||
|
|
||||||
|
def generate_image(prompt, **kwargs):
|
||||||
|
"""
|
||||||
|
Generate image using Stable Diffusion / OpenAI compatible API
|
||||||
|
"""
|
||||||
|
base_url = "http://toybox.reeselink.com:1234/v1"
|
||||||
|
|
||||||
|
payload = {"model": "default", "prompt": prompt, "n": 1, "size": "1024x1024"}
|
||||||
|
|
||||||
|
response = requests.post(
|
||||||
|
f"http://toybox.reeselink.com:1234/v1/images/generations",
|
||||||
|
json=payload,
|
||||||
|
timeout=120,
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
# Save image
|
||||||
|
image_data = base64.b64decode(result["data"][0]["b64_json"])
|
||||||
|
img = Image.open(BytesIO(image_data))
|
||||||
|
filename = f"generated_{datetime.now().strftime('%Y%m%d_%H%M%S')}.png"
|
||||||
|
img.save(filename)
|
||||||
|
print(f"✅ Saved: {filename}")
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
print(f"❌ Error: {response.status_code}")
|
||||||
|
print(response.text)
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
# Usage:
|
||||||
|
result = generate_image(
|
||||||
|
prompt="A beautiful sunset over mountains, photorealistic",
|
||||||
|
negative_prompt="blurry, low quality",
|
||||||
|
steps=8,
|
||||||
|
guidance=7.5,
|
||||||
|
)
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
[Unit]
|
|
||||||
Description=An Open Webui Frontend for Local AI Services for Guests
|
|
||||||
|
|
||||||
[Container]
|
|
||||||
# Shared AI external pod
|
|
||||||
Pod=ai-external.pod
|
|
||||||
|
|
||||||
# Open Webui base image
|
|
||||||
Image=ghcr.io/open-webui/open-webui:main
|
|
||||||
|
|
||||||
# Nothing too complicated here. Open Webui will basically configure itself.
|
|
||||||
Volume=open-webui-data-guest:/app/backend/data
|
|
||||||
|
|
||||||
# WEBUI_SECRET_KEY is required to prevent logout on Restart
|
|
||||||
EnvironmentFile=/home/ai/.env/open-webui-env-guest
|
|
||||||
|
|
||||||
# ai-external is the primary network
|
|
||||||
Network=ai-external.network
|
|
||||||
Network=ai-internal.network
|
|
||||||
|
|
||||||
# open-webui
|
|
||||||
PublishPort=8081:8081/tcp
|
|
||||||
|
|
||||||
[Service]
|
|
||||||
Restart=on-failure
|
|
||||||
RestartSec=5
|
|
||||||
# Extend Timeout to allow time to pull the image
|
|
||||||
TimeoutStartSec=900
|
|
||||||
|
|
||||||
[Install]
|
|
||||||
# Start by default on boot
|
|
||||||
WantedBy=multi-user.target default.target
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
[Network]
|
|
||||||
IPv6=true
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
[Pod]
|
|
||||||
# ai-external is the primary network
|
|
||||||
Network=ai-external.network
|
|
||||||
Network=ai-internal.network
|
|
||||||
# open-webui
|
|
||||||
PublishPort=8080:8080/tcp
|
|
||||||
@@ -9,12 +9,19 @@ fedora:
|
|||||||
minecraft:
|
minecraft:
|
||||||
borg-root:
|
borg-root:
|
||||||
elk:
|
elk:
|
||||||
|
toybox-root:
|
||||||
|
|
||||||
hardware:
|
hardware:
|
||||||
hosts:
|
hosts:
|
||||||
deskwork-root:
|
deskwork-root:
|
||||||
driveripper:
|
driveripper:
|
||||||
|
|
||||||
|
ai:
|
||||||
|
hosts:
|
||||||
|
ai-ai:
|
||||||
|
deskwork-ai:
|
||||||
|
toybox-ai:
|
||||||
|
|
||||||
caddy:
|
caddy:
|
||||||
hosts:
|
hosts:
|
||||||
proxy:
|
proxy:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ dependencies = [
|
|||||||
"mkdocs>=1.6.1",
|
"mkdocs>=1.6.1",
|
||||||
"openai>=2.21.0",
|
"openai>=2.21.0",
|
||||||
"pika>=1.3.2",
|
"pika>=1.3.2",
|
||||||
|
"pillow>=12.1.1",
|
||||||
"pytest>=9.0.2",
|
"pytest>=9.0.2",
|
||||||
"pyyaml>=6.0.3",
|
"pyyaml>=6.0.3",
|
||||||
"requests>=2.32.5",
|
"requests>=2.32.5",
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ Run the following to convert a compose.yaml into the various `.container` files
|
|||||||
podman run \
|
podman run \
|
||||||
--security-opt label=disable \
|
--security-opt label=disable \
|
||||||
--rm \
|
--rm \
|
||||||
-v $(pwd)/active/container_foobar/:/compose \
|
-v $(pwd)/active/container_foobar/compose:/compose \
|
||||||
-v $(pwd)/active/container_foobar/quadlets:/quadlets \
|
-v $(pwd)/active/container_foobar/quadlets:/quadlets \
|
||||||
quay.io/k9withabone/podlet \
|
quay.io/k9withabone/podlet \
|
||||||
-f /quadlets \
|
-f /quadlets \
|
||||||
|
|||||||
60
uv.lock
generated
60
uv.lock
generated
@@ -133,6 +133,7 @@ dependencies = [
|
|||||||
{ name = "mkdocs" },
|
{ name = "mkdocs" },
|
||||||
{ name = "openai" },
|
{ name = "openai" },
|
||||||
{ name = "pika" },
|
{ name = "pika" },
|
||||||
|
{ name = "pillow" },
|
||||||
{ name = "pytest" },
|
{ name = "pytest" },
|
||||||
{ name = "pyyaml" },
|
{ name = "pyyaml" },
|
||||||
{ name = "requests" },
|
{ name = "requests" },
|
||||||
@@ -147,6 +148,7 @@ requires-dist = [
|
|||||||
{ name = "mkdocs", specifier = ">=1.6.1" },
|
{ name = "mkdocs", specifier = ">=1.6.1" },
|
||||||
{ name = "openai", specifier = ">=2.21.0" },
|
{ name = "openai", specifier = ">=2.21.0" },
|
||||||
{ name = "pika", specifier = ">=1.3.2" },
|
{ name = "pika", specifier = ">=1.3.2" },
|
||||||
|
{ name = "pillow", specifier = ">=12.1.1" },
|
||||||
{ name = "pytest", specifier = ">=9.0.2" },
|
{ name = "pytest", specifier = ">=9.0.2" },
|
||||||
{ name = "pyyaml", specifier = ">=6.0.3" },
|
{ name = "pyyaml", specifier = ">=6.0.3" },
|
||||||
{ name = "requests", specifier = ">=2.32.5" },
|
{ name = "requests", specifier = ">=2.32.5" },
|
||||||
@@ -418,6 +420,64 @@ wheels = [
|
|||||||
{ url = "https://files.pythonhosted.org/packages/f9/f3/f412836ec714d36f0f4ab581b84c491e3f42c6b5b97a6c6ed1817f3c16d0/pika-1.3.2-py3-none-any.whl", hash = "sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f", size = 155415, upload-time = "2023-05-05T14:25:41.484Z" },
|
{ url = "https://files.pythonhosted.org/packages/f9/f3/f412836ec714d36f0f4ab581b84c491e3f42c6b5b97a6c6ed1817f3c16d0/pika-1.3.2-py3-none-any.whl", hash = "sha256:0779a7c1fafd805672796085560d290213a465e4f6f76a6fb19e378d8041a14f", size = 155415, upload-time = "2023-05-05T14:25:41.484Z" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pillow"
|
||||||
|
version = "12.1.1"
|
||||||
|
source = { registry = "https://pypi.org/simple" }
|
||||||
|
sdist = { url = "https://files.pythonhosted.org/packages/1f/42/5c74462b4fd957fcd7b13b04fb3205ff8349236ea74c7c375766d6c82288/pillow-12.1.1.tar.gz", hash = "sha256:9ad8fa5937ab05218e2b6a4cff30295ad35afd2f83ac592e68c0d871bb0fdbc4", size = 46980264, upload-time = "2026-02-11T04:23:07.146Z" }
|
||||||
|
wheels = [
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/11/6db24d4bd7685583caeae54b7009584e38da3c3d4488ed4cd25b439de486/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphoneos.whl", hash = "sha256:d242e8ac078781f1de88bf823d70c1a9b3c7950a44cdf4b7c012e22ccbcd8e4e", size = 4062689, upload-time = "2026-02-11T04:21:06.804Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/33/c0/ce6d3b1fe190f0021203e0d9b5b99e57843e345f15f9ef22fcd43842fd21/pillow-12.1.1-cp313-cp313-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:02f84dfad02693676692746df05b89cf25597560db2857363a208e393429f5e9", size = 4138535, upload-time = "2026-02-11T04:21:08.452Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a0/c6/d5eb6a4fb32a3f9c21a8c7613ec706534ea1cf9f4b3663e99f0d83f6fca8/pillow-12.1.1-cp313-cp313-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:e65498daf4b583091ccbb2556c7000abf0f3349fcd57ef7adc9a84a394ed29f6", size = 3601364, upload-time = "2026-02-11T04:21:10.194Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/14/a1/16c4b823838ba4c9c52c0e6bbda903a3fe5a1bdbf1b8eb4fff7156f3e318/pillow-12.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:6c6db3b84c87d48d0088943bf33440e0c42370b99b1c2a7989216f7b42eede60", size = 5262561, upload-time = "2026-02-11T04:21:11.742Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/bb/ad/ad9dc98ff24f485008aa5cdedaf1a219876f6f6c42a4626c08bc4e80b120/pillow-12.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:8b7e5304e34942bf62e15184219a7b5ad4ff7f3bb5cca4d984f37df1a0e1aee2", size = 4657460, upload-time = "2026-02-11T04:21:13.786Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/9e/1b/f1a4ea9a895b5732152789326202a82464d5254759fbacae4deea3069334/pillow-12.1.1-cp313-cp313-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:18e5bddd742a44b7e6b1e773ab5db102bd7a94c32555ba656e76d319d19c3850", size = 6232698, upload-time = "2026-02-11T04:21:15.949Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/95/f4/86f51b8745070daf21fd2e5b1fe0eb35d4db9ca26e6d58366562fb56a743/pillow-12.1.1-cp313-cp313-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:fc44ef1f3de4f45b50ccf9136999d71abb99dca7706bc75d222ed350b9fd2289", size = 8041706, upload-time = "2026-02-11T04:21:17.723Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/29/9b/d6ecd956bb1266dd1045e995cce9b8d77759e740953a1c9aad9502a0461e/pillow-12.1.1-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:5a8eb7ed8d4198bccbd07058416eeec51686b498e784eda166395a23eb99138e", size = 6346621, upload-time = "2026-02-11T04:21:19.547Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/71/24/538bff45bde96535d7d998c6fed1a751c75ac7c53c37c90dc2601b243893/pillow-12.1.1-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:47b94983da0c642de92ced1702c5b6c292a84bd3a8e1d1702ff923f183594717", size = 7038069, upload-time = "2026-02-11T04:21:21.378Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/94/0e/58cb1a6bc48f746bc4cb3adb8cabff73e2742c92b3bf7a220b7cf69b9177/pillow-12.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:518a48c2aab7ce596d3bf79d0e275661b846e86e4d0e7dec34712c30fe07f02a", size = 6460040, upload-time = "2026-02-11T04:21:23.148Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/57/9045cb3ff11eeb6c1adce3b2d60d7d299d7b273a2e6c8381a524abfdc474/pillow-12.1.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:a550ae29b95c6dc13cf69e2c9dc5747f814c54eeb2e32d683e5e93af56caa029", size = 7164523, upload-time = "2026-02-11T04:21:25.01Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/f2/9be9cb99f2175f0d4dbadd6616ce1bf068ee54a28277ea1bf1fbf729c250/pillow-12.1.1-cp313-cp313-win32.whl", hash = "sha256:a003d7422449f6d1e3a34e3dd4110c22148336918ddbfc6a32581cd54b2e0b2b", size = 6332552, upload-time = "2026-02-11T04:21:27.238Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3f/eb/b0834ad8b583d7d9d42b80becff092082a1c3c156bb582590fcc973f1c7c/pillow-12.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:344cf1e3dab3be4b1fa08e449323d98a2a3f819ad20f4b22e77a0ede31f0faa1", size = 7040108, upload-time = "2026-02-11T04:21:29.462Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d5/7d/fc09634e2aabdd0feabaff4a32f4a7d97789223e7c2042fd805ea4b4d2c2/pillow-12.1.1-cp313-cp313-win_arm64.whl", hash = "sha256:5c0dd1636633e7e6a0afe7bf6a51a14992b7f8e60de5789018ebbdfae55b040a", size = 2453712, upload-time = "2026-02-11T04:21:31.072Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/19/2a/b9d62794fc8a0dd14c1943df68347badbd5511103e0d04c035ffe5cf2255/pillow-12.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:0330d233c1a0ead844fc097a7d16c0abff4c12e856c0b325f231820fee1f39da", size = 5264880, upload-time = "2026-02-11T04:21:32.865Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/26/9d/e03d857d1347fa5ed9247e123fcd2a97b6220e15e9cb73ca0a8d91702c6e/pillow-12.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:5dae5f21afb91322f2ff791895ddd8889e5e947ff59f71b46041c8ce6db790bc", size = 4660616, upload-time = "2026-02-11T04:21:34.97Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f7/ec/8a6d22afd02570d30954e043f09c32772bfe143ba9285e2fdb11284952cd/pillow-12.1.1-cp313-cp313t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:2e0c664be47252947d870ac0d327fea7e63985a08794758aa8af5b6cb6ec0c9c", size = 6269008, upload-time = "2026-02-11T04:21:36.623Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3d/1d/6d875422c9f28a4a361f495a5f68d9de4a66941dc2c619103ca335fa6446/pillow-12.1.1-cp313-cp313t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:691ab2ac363b8217f7d31b3497108fb1f50faab2f75dfb03284ec2f217e87bf8", size = 8073226, upload-time = "2026-02-11T04:21:38.585Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a1/cd/134b0b6ee5eda6dc09e25e24b40fdafe11a520bc725c1d0bbaa5e00bf95b/pillow-12.1.1-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:e9e8064fb1cc019296958595f6db671fba95209e3ceb0c4734c9baf97de04b20", size = 6380136, upload-time = "2026-02-11T04:21:40.562Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/7a/a9/7628f013f18f001c1b98d8fffe3452f306a70dc6aba7d931019e0492f45e/pillow-12.1.1-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:472a8d7ded663e6162dafdf20015c486a7009483ca671cece7a9279b512fcb13", size = 7067129, upload-time = "2026-02-11T04:21:42.521Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/1e/f8/66ab30a2193b277785601e82ee2d49f68ea575d9637e5e234faaa98efa4c/pillow-12.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:89b54027a766529136a06cfebeecb3a04900397a3590fd252160b888479517bf", size = 6491807, upload-time = "2026-02-11T04:21:44.22Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/da/0b/a877a6627dc8318fdb84e357c5e1a758c0941ab1ddffdafd231983788579/pillow-12.1.1-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:86172b0831b82ce4f7877f280055892b31179e1576aa00d0df3bb1bbf8c3e524", size = 7190954, upload-time = "2026-02-11T04:21:46.114Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/83/43/6f732ff85743cf746b1361b91665d9f5155e1483817f693f8d57ea93147f/pillow-12.1.1-cp313-cp313t-win32.whl", hash = "sha256:44ce27545b6efcf0fdbdceb31c9a5bdea9333e664cda58a7e674bb74608b3986", size = 6336441, upload-time = "2026-02-11T04:21:48.22Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/3b/44/e865ef3986611bb75bfabdf94a590016ea327833f434558801122979cd0e/pillow-12.1.1-cp313-cp313t-win_amd64.whl", hash = "sha256:a285e3eb7a5a45a2ff504e31f4a8d1b12ef62e84e5411c6804a42197c1cf586c", size = 7045383, upload-time = "2026-02-11T04:21:50.015Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a8/c6/f4fb24268d0c6908b9f04143697ea18b0379490cb74ba9e8d41b898bd005/pillow-12.1.1-cp313-cp313t-win_arm64.whl", hash = "sha256:cc7d296b5ea4d29e6570dabeaed58d31c3fea35a633a69679fb03d7664f43fb3", size = 2456104, upload-time = "2026-02-11T04:21:51.633Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/03/d0/bebb3ffbf31c5a8e97241476c4cf8b9828954693ce6744b4a2326af3e16b/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphoneos.whl", hash = "sha256:417423db963cb4be8bac3fc1204fe61610f6abeed1580a7a2cbb2fbda20f12af", size = 4062652, upload-time = "2026-02-11T04:21:53.19Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/2d/c0/0e16fb0addda4851445c28f8350d8c512f09de27bbb0d6d0bbf8b6709605/pillow-12.1.1-cp314-cp314-ios_13_0_arm64_iphonesimulator.whl", hash = "sha256:b957b71c6b2387610f556a7eb0828afbe40b4a98036fc0d2acfa5a44a0c2036f", size = 4138823, upload-time = "2026-02-11T04:22:03.088Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6b/fb/6170ec655d6f6bb6630a013dd7cf7bc218423d7b5fa9071bf63dc32175ae/pillow-12.1.1-cp314-cp314-ios_13_0_x86_64_iphonesimulator.whl", hash = "sha256:097690ba1f2efdeb165a20469d59d8bb03c55fb6621eb2041a060ae8ea3e9642", size = 3601143, upload-time = "2026-02-11T04:22:04.909Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/59/04/dc5c3f297510ba9a6837cbb318b87dd2b8f73eb41a43cc63767f65cb599c/pillow-12.1.1-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:2815a87ab27848db0321fb78c7f0b2c8649dee134b7f2b80c6a45c6831d75ccd", size = 5266254, upload-time = "2026-02-11T04:22:07.656Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/05/30/5db1236b0d6313f03ebf97f5e17cda9ca060f524b2fcc875149a8360b21c/pillow-12.1.1-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:f7ed2c6543bad5a7d5530eb9e78c53132f93dfa44a28492db88b41cdab885202", size = 4657499, upload-time = "2026-02-11T04:22:09.613Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6f/18/008d2ca0eb612e81968e8be0bbae5051efba24d52debf930126d7eaacbba/pillow-12.1.1-cp314-cp314-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:652a2c9ccfb556235b2b501a3a7cf3742148cd22e04b5625c5fe057ea3e3191f", size = 6232137, upload-time = "2026-02-11T04:22:11.434Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/70/f1/f14d5b8eeb4b2cd62b9f9f847eb6605f103df89ef619ac68f92f748614ea/pillow-12.1.1-cp314-cp314-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:d6e4571eedf43af33d0fc233a382a76e849badbccdf1ac438841308652a08e1f", size = 8042721, upload-time = "2026-02-11T04:22:13.321Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/5a/d6/17824509146e4babbdabf04d8171491fa9d776f7061ff6e727522df9bd03/pillow-12.1.1-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:b574c51cf7d5d62e9be37ba446224b59a2da26dc4c1bb2ecbe936a4fb1a7cb7f", size = 6347798, upload-time = "2026-02-11T04:22:15.449Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/d1/ee/c85a38a9ab92037a75615aba572c85ea51e605265036e00c5b67dfafbfe2/pillow-12.1.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:a37691702ed687799de29a518d63d4682d9016932db66d4e90c345831b02fb4e", size = 7039315, upload-time = "2026-02-11T04:22:17.24Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/f3/bc8ccc6e08a148290d7523bde4d9a0d6c981db34631390dc6e6ec34cacf6/pillow-12.1.1-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:f95c00d5d6700b2b890479664a06e754974848afaae5e21beb4d83c106923fd0", size = 6462360, upload-time = "2026-02-11T04:22:19.111Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f6/ab/69a42656adb1d0665ab051eec58a41f169ad295cf81ad45406963105408f/pillow-12.1.1-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:559b38da23606e68681337ad74622c4dbba02254fc9cb4488a305dd5975c7eeb", size = 7165438, upload-time = "2026-02-11T04:22:21.041Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/02/46/81f7aa8941873f0f01d4b55cc543b0a3d03ec2ee30d617a0448bf6bd6dec/pillow-12.1.1-cp314-cp314-win32.whl", hash = "sha256:03edcc34d688572014ff223c125a3f77fb08091e4607e7745002fc214070b35f", size = 6431503, upload-time = "2026-02-11T04:22:22.833Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/40/72/4c245f7d1044b67affc7f134a09ea619d4895333d35322b775b928180044/pillow-12.1.1-cp314-cp314-win_amd64.whl", hash = "sha256:50480dcd74fa63b8e78235957d302d98d98d82ccbfac4c7e12108ba9ecbdba15", size = 7176748, upload-time = "2026-02-11T04:22:24.64Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/e4/ad/8a87bdbe038c5c698736e3348af5c2194ffb872ea52f11894c95f9305435/pillow-12.1.1-cp314-cp314-win_arm64.whl", hash = "sha256:5cb1785d97b0c3d1d1a16bc1d710c4a0049daefc4935f3a8f31f827f4d3d2e7f", size = 2544314, upload-time = "2026-02-11T04:22:26.685Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/6c/9d/efd18493f9de13b87ede7c47e69184b9e859e4427225ea962e32e56a49bc/pillow-12.1.1-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:1f90cff8aa76835cba5769f0b3121a22bd4eb9e6884cfe338216e557a9a548b8", size = 5268612, upload-time = "2026-02-11T04:22:29.884Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f8/f1/4f42eb2b388eb2ffc660dcb7f7b556c1015c53ebd5f7f754965ef997585b/pillow-12.1.1-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:1f1be78ce9466a7ee64bfda57bdba0f7cc499d9794d518b854816c41bf0aa4e9", size = 4660567, upload-time = "2026-02-11T04:22:31.799Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/54/df6ef130fa43e4b82e32624a7b821a2be1c5653a5fdad8469687a7db4e00/pillow-12.1.1-cp314-cp314t-manylinux2014_aarch64.manylinux_2_17_aarch64.whl", hash = "sha256:42fc1f4677106188ad9a55562bbade416f8b55456f522430fadab3cef7cd4e60", size = 6269951, upload-time = "2026-02-11T04:22:33.921Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/a9/48/618752d06cc44bb4aae8ce0cd4e6426871929ed7b46215638088270d9b34/pillow-12.1.1-cp314-cp314t-manylinux2014_x86_64.manylinux_2_17_x86_64.whl", hash = "sha256:98edb152429ab62a1818039744d8fbb3ccab98a7c29fc3d5fcef158f3f1f68b7", size = 8074769, upload-time = "2026-02-11T04:22:35.877Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c3/bd/f1d71eb39a72fa088d938655afba3e00b38018d052752f435838961127d8/pillow-12.1.1-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:d470ab1178551dd17fdba0fef463359c41aaa613cdcd7ff8373f54be629f9f8f", size = 6381358, upload-time = "2026-02-11T04:22:37.698Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/64/ef/c784e20b96674ed36a5af839305f55616f8b4f8aa8eeccf8531a6e312243/pillow-12.1.1-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:6408a7b064595afcab0a49393a413732a35788f2a5092fdc6266952ed67de586", size = 7068558, upload-time = "2026-02-11T04:22:39.597Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/73/cb/8059688b74422ae61278202c4e1ad992e8a2e7375227be0a21c6b87ca8d5/pillow-12.1.1-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:5d8c41325b382c07799a3682c1c258469ea2ff97103c53717b7893862d0c98ce", size = 6493028, upload-time = "2026-02-11T04:22:42.73Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/c6/da/e3c008ed7d2dd1f905b15949325934510b9d1931e5df999bb15972756818/pillow-12.1.1-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:c7697918b5be27424e9ce568193efd13d925c4481dd364e43f5dff72d33e10f8", size = 7191940, upload-time = "2026-02-11T04:22:44.543Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/01/4a/9202e8d11714c1fc5951f2e1ef362f2d7fbc595e1f6717971d5dd750e969/pillow-12.1.1-cp314-cp314t-win32.whl", hash = "sha256:d2912fd8114fc5545aa3a4b5576512f64c55a03f3ebcca4c10194d593d43ea36", size = 6438736, upload-time = "2026-02-11T04:22:46.347Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/f3/ca/cbce2327eb9885476b3957b2e82eb12c866a8b16ad77392864ad601022ce/pillow-12.1.1-cp314-cp314t-win_amd64.whl", hash = "sha256:4ceb838d4bd9dab43e06c363cab2eebf63846d6a4aeaea283bbdfd8f1a8ed58b", size = 7182894, upload-time = "2026-02-11T04:22:48.114Z" },
|
||||||
|
{ url = "https://files.pythonhosted.org/packages/ec/d2/de599c95ba0a973b94410477f8bf0b6f0b5e67360eb89bcb1ad365258beb/pillow-12.1.1-cp314-cp314t-win_arm64.whl", hash = "sha256:7b03048319bfc6170e93bd60728a1af51d3dd7704935feb228c4d4faab35d334", size = 2546446, upload-time = "2026-02-11T04:22:50.342Z" },
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "platformdirs"
|
name = "platformdirs"
|
||||||
version = "4.9.1"
|
version = "4.9.1"
|
||||||
|
|||||||
Reference in New Issue
Block a user