mini-apigw 0.0.6__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mini_apigw-0.0.6/LICENSE.md +24 -0
- mini_apigw-0.0.6/PKG-INFO +313 -0
- mini_apigw-0.0.6/README.md +284 -0
- mini_apigw-0.0.6/gateway/__init__.py +3 -0
- mini_apigw-0.0.6/gateway/accounting.py +252 -0
- mini_apigw-0.0.6/gateway/api/__init__.py +1 -0
- mini_apigw-0.0.6/gateway/api/admin.py +113 -0
- mini_apigw-0.0.6/gateway/api/v1.py +359 -0
- mini_apigw-0.0.6/gateway/app.py +60 -0
- mini_apigw-0.0.6/gateway/auth.py +55 -0
- mini_apigw-0.0.6/gateway/backends/__init__.py +27 -0
- mini_apigw-0.0.6/gateway/backends/anthropic.py +66 -0
- mini_apigw-0.0.6/gateway/backends/base.py +69 -0
- mini_apigw-0.0.6/gateway/backends/ollama.py +558 -0
- mini_apigw-0.0.6/gateway/backends/openai.py +65 -0
- mini_apigw-0.0.6/gateway/cli.py +279 -0
- mini_apigw-0.0.6/gateway/config.py +571 -0
- mini_apigw-0.0.6/gateway/ipacl.py +25 -0
- mini_apigw-0.0.6/gateway/log.py +51 -0
- mini_apigw-0.0.6/gateway/main.py +8 -0
- mini_apigw-0.0.6/gateway/middleware/__init__.py +0 -0
- mini_apigw-0.0.6/gateway/middleware/trace.py +48 -0
- mini_apigw-0.0.6/gateway/policies.py +33 -0
- mini_apigw-0.0.6/gateway/routing.py +146 -0
- mini_apigw-0.0.6/gateway/runtime.py +223 -0
- mini_apigw-0.0.6/gateway/scheduling.py +131 -0
- mini_apigw-0.0.6/gateway/signals.py +34 -0
- mini_apigw-0.0.6/gateway/sse.py +28 -0
- mini_apigw-0.0.6/gateway/trace.py +186 -0
- mini_apigw-0.0.6/mini_apigw.egg-info/PKG-INFO +313 -0
- mini_apigw-0.0.6/mini_apigw.egg-info/SOURCES.txt +39 -0
- mini_apigw-0.0.6/mini_apigw.egg-info/dependency_links.txt +1 -0
- mini_apigw-0.0.6/mini_apigw.egg-info/entry_points.txt +2 -0
- mini_apigw-0.0.6/mini_apigw.egg-info/requires.txt +9 -0
- mini_apigw-0.0.6/mini_apigw.egg-info/top_level.txt +1 -0
- mini_apigw-0.0.6/pyproject.toml +64 -0
- mini_apigw-0.0.6/setup.cfg +4 -0
- mini_apigw-0.0.6/tests/test_config.py +101 -0
- mini_apigw-0.0.6/tests/test_policies.py +21 -0
- mini_apigw-0.0.6/tests/test_routing.py +11 -0
- mini_apigw-0.0.6/tests/test_scheduling.py +51 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Copyright 2025, Thomas Spielauer
|
|
2
|
+
|
|
3
|
+
Redistribution and use in source and binary forms, with or without modification,
|
|
4
|
+
are permitted provided that the following conditions are met:
|
|
5
|
+
|
|
6
|
+
* Redistributions of source code must retain this list of conditions and the
|
|
7
|
+
following disclaimer.
|
|
8
|
+
* Redistributions in binary form must reproduce this list of conditions and the
|
|
9
|
+
following disclaimer in the documentation and/or other materials provided with
|
|
10
|
+
the distribution.
|
|
11
|
+
* Neither the name of the copyright holder nor the names of its contributors may
|
|
12
|
+
be used to endorse or promote products derived from this software without
|
|
13
|
+
specific prior written permission.
|
|
14
|
+
|
|
15
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
16
|
+
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
17
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
18
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
|
19
|
+
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
20
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
21
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
22
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
23
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
24
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mini-apigw
|
|
3
|
+
Version: 0.0.6
|
|
4
|
+
Summary: Minimal OpenAI-compatible edge gateway with multi-backend routing
|
|
5
|
+
Author-email: Thomas Spielauer <pypipackages01@tspi.at>
|
|
6
|
+
License-Expression: BSD-3-Clause
|
|
7
|
+
Project-URL: Homepage, https://www.github.com/tspspi/mini-apigw
|
|
8
|
+
Keywords: API-gateway,gateway,API,LLM
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
16
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Description-Content-Type: text/markdown
|
|
19
|
+
License-File: LICENSE.md
|
|
20
|
+
Requires-Dist: fastapi>=0.110
|
|
21
|
+
Requires-Dist: uvicorn>=0.27
|
|
22
|
+
Requires-Dist: requests>=2.31
|
|
23
|
+
Requires-Dist: openai>=1.0
|
|
24
|
+
Requires-Dist: ollama>=0.3
|
|
25
|
+
Requires-Dist: psycopg2>=2.9.10
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
28
|
+
Dynamic: license-file
|
|
29
|
+
|
|
30
|
+
# mini-apigw — Minimal OpenAI‑compatible API Gateway
|
|
31
|
+
|
|
32
|
+
__WORK IN PROGRESS_
|
|
33
|
+
|
|
34
|
+
mini-apigw is a small edge gateway that presents an OpenAI‑compatible API surface and routes requests
|
|
35
|
+
to multiple LLM and image generation backends. It is designed for __simplicity__ and ease of control: you
|
|
36
|
+
configure backends and apps in JSON, set policies and cost limits per app, and the gateway handles
|
|
37
|
+
routing, scheduling, usage accounting, optional persistence, trace logging, and admin endpoints.
|
|
38
|
+
|
|
39
|
+
The main reason I developed this gateway was to handle multiple access to shared resources - I personally
|
|
40
|
+
utilize machines that run LLMs (ollama, vllm) as well as SDXL based image generation and other software. They
|
|
41
|
+
compete for GPU resources and since those tools are usually not developed to work together nicely and
|
|
42
|
+
arbitrate GPU usage the gateway offers serialization of requests inside _sequence groups_. All backends
|
|
43
|
+
in the same sequence group execute requests of all backends in the same group strictly in sequence so the
|
|
44
|
+
local backends can handle loading and unloading of competing backends.
|
|
45
|
+
|
|
46
|
+
Backends included out of the box:
|
|
47
|
+
|
|
48
|
+
* OpenAI
|
|
49
|
+
* Ollama
|
|
50
|
+
|
|
51
|
+
In development:
|
|
52
|
+
|
|
53
|
+
* Anthropic
|
|
54
|
+
* Fooocus
|
|
55
|
+
* vLLM
|
|
56
|
+
|
|
57
|
+
The gateway exposes the familiar `/v1` endpoints (`/chat/completions`, `/completions`,
|
|
58
|
+
`/embeddings`, `/images/generations`, and `/models`) and normalizes responses where
|
|
59
|
+
needed. This allows one to use the ```openai``` client library for any backend. Note that the gateway
|
|
60
|
+
does not implement the full OpenAI API - it just passes through the above mentioned
|
|
61
|
+
endpoints. In addition it uses local API keys. Those API keys are used to select the application
|
|
62
|
+
from ```apps.json``` configuration file.
|
|
63
|
+
|
|
64
|
+
## Installation
|
|
65
|
+
|
|
66
|
+
From PyPI (recommended):
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
pip install mini-apigw
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
From source (editable):
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install -e .
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Configuration
|
|
79
|
+
|
|
80
|
+
The gateway reads three JSON files from a configuration directory. By default this
|
|
81
|
+
is `./config` (at the moment, this will be fixed soon, this will be a breaking change!),
|
|
82
|
+
or override with the environment variable `MINIAPIGW_CONFIG_DIR` or the CLI flag `--config-dir`.
|
|
83
|
+
|
|
84
|
+
Required files:
|
|
85
|
+
|
|
86
|
+
- `daemon.json`: service, logging, admin, timeouts, and optional Postgres settings
|
|
87
|
+
- `backends.json`: model providers, aliases, costs, and capabilities
|
|
88
|
+
- `apps.json`: application definitions, API keys, allow/deny policies, cost limits, and tracing
|
|
89
|
+
|
|
90
|
+
Example `daemon.json` (minimal, no persistent accounting log):
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"listen": { "host_v4": "0.0.0.0", "port": 8080 },
|
|
95
|
+
"admin": { "bind": ["127.0.0.1:8081", "[::1]:8081"], "stats_networks": ["127.0.0.1/32", "::1/128"] },
|
|
96
|
+
"logging": { "level": "INFO", "redact_prompts": true, "access_log": true },
|
|
97
|
+
"reload": { "enable_sighup": true },
|
|
98
|
+
"timeouts": { "default_connect_s": 60, "default_read_s": 600 },
|
|
99
|
+
"database": null
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
Example `backends.json` (mixed OpenAI + Ollama):
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"aliases": { "llama3.2": "llama3.2:latest" },
|
|
108
|
+
"sequence_groups": { "local_gpu_01": { "description": "Serialized work for local GPU tasks" } },
|
|
109
|
+
"backends": [
|
|
110
|
+
{
|
|
111
|
+
"type": "openai",
|
|
112
|
+
"name": "openai-primary",
|
|
113
|
+
"base_url": "https://api.openai.com/v1",
|
|
114
|
+
"api_key": "<openai_key>",
|
|
115
|
+
"concurrency": 4,
|
|
116
|
+
"supports": { "chat": ["gpt-4o-mini"], "embeddings": ["text-embedding-3-small"], "images": ["gpt-image-1", "dall-e-3"] },
|
|
117
|
+
"cost": { "currency": "usd", "unit": "1k_tokens", "models": { "gpt-4o-mini": {"prompt": 0.002, "completion": 0.004} } }
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"type": "ollama",
|
|
121
|
+
"name": "ollama-local",
|
|
122
|
+
"base_url": "http://127.0.0.1:11434",
|
|
123
|
+
"sequence_group": "local_gpu_01",
|
|
124
|
+
"concurrency": 1,
|
|
125
|
+
"supports": { "chat": ["llama3.2:latest", "gpt-oss:120b"], "completions": ["llama3.2:latest"], "embeddings": ["nomic-embed-text"] },
|
|
126
|
+
"cost": {
|
|
127
|
+
"models": {
|
|
128
|
+
"llama3.2:latest": {"prompt": 0.0, "completion": 0.0},
|
|
129
|
+
"gpt-oss:120b": {"prompt": 0.001, "completion": 0.001 }
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
]
|
|
134
|
+
}
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
Example `apps.json` (one app, you just declare them one after each other), the API keys are threatened
|
|
138
|
+
transparent, you can use any string as Bearer token, it just has to be unique; the app ID is used in filemanes
|
|
139
|
+
so you might not want to use special characters:
|
|
140
|
+
|
|
141
|
+
```json
|
|
142
|
+
{
|
|
143
|
+
"apps": [
|
|
144
|
+
{
|
|
145
|
+
"app_id": "demo",
|
|
146
|
+
"name": "Demo application",
|
|
147
|
+
"api_keys": [
|
|
148
|
+
"sk-example-key"
|
|
149
|
+
],
|
|
150
|
+
"policy": {
|
|
151
|
+
"allow": [ "gpt-4o-mini", "llama3.2" ],
|
|
152
|
+
"deny": []
|
|
153
|
+
},
|
|
154
|
+
"cost_limit": {
|
|
155
|
+
"period": "day",
|
|
156
|
+
"limit": 10.0
|
|
157
|
+
},
|
|
158
|
+
"trace": {
|
|
159
|
+
"file": "/var/log/llmgw/demo.jsonl",
|
|
160
|
+
"image_dir": "/var/log/llmgw/images/demo",
|
|
161
|
+
"include_prompts": true,
|
|
162
|
+
"include_response": true,
|
|
163
|
+
"include_keys": true
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Notes:
|
|
171
|
+
|
|
172
|
+
- Backends declare capabilities via `supports` (globs allowed) and optional `aliases`.
|
|
173
|
+
Use `concurrency` and `sequence_group` to tune throughput/serialization. Costs
|
|
174
|
+
under `cost` are used to estimate per‑app spend. This may of course deviate from real
|
|
175
|
+
platform billing. It's just there to provide an estimate (and may be reworked later on).
|
|
176
|
+
Also billing is currently not tracked for images.
|
|
177
|
+
- Apps bind one or more API keys to an `app_id`, use `policy.allow`/`policy.deny` to
|
|
178
|
+
restrict models, and `cost_limit` to enforce soft limits. Per‑app traces can be persisted
|
|
179
|
+
as JSONL with optional image capture.
|
|
180
|
+
- The admin interface binds on the same port (due to limitations in FastAPI code and simplicity).
|
|
181
|
+
It is restricted to localhost by default, beware of limitations of this kind of filtering
|
|
182
|
+
when running local jails; additional networks can be allow‑listed with CIDRs in `admin.stats_networks`.
|
|
183
|
+
|
|
184
|
+
## Running
|
|
185
|
+
|
|
186
|
+
The package installs a console entry point `mini-apigw`.
|
|
187
|
+
|
|
188
|
+
Foreground server_
|
|
189
|
+
|
|
190
|
+
```bash
|
|
191
|
+
mini-apigw start --config-dir ./config --foreground --reload
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Daemonize with defaults from `daemon.json` (port/host or Unix socket):
|
|
195
|
+
|
|
196
|
+
```bash
|
|
197
|
+
mini-apigw start --config-dir ./config
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
Override listener explicitly:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
mini-apigw start --config-dir ./config --host 0.0.0.0 --port 8080
|
|
204
|
+
# or use a Unix domain socket
|
|
205
|
+
mini-apigw start --config-dir ./config --unix-socket /var/llmgw/llmgw.sock
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Admin helpers (call built‑in admin endpoints):
|
|
209
|
+
|
|
210
|
+
```bash
|
|
211
|
+
mini-apigw reload --config-dir ./config
|
|
212
|
+
mini-apigw stop --config-dir ./config
|
|
213
|
+
mini-apigw token --bytes 32 # generate a random API key
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## OpenAI‑Compatible API
|
|
217
|
+
|
|
218
|
+
All endpoints live under `/v1` and require `Authorization: Bearer <api_key>`.
|
|
219
|
+
|
|
220
|
+
List models (combines declared and auto‑discovered where available):
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
curl -s -H "Authorization: Bearer sk-demo-key" http://127.0.0.1:8080/v1/models | jq .
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
Chat completions (JSON response; set `stream: true` for SSE):
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
curl -s \
|
|
230
|
+
-H "Authorization: Bearer sk-demo-key" \
|
|
231
|
+
-H "Content-Type: application/json" \
|
|
232
|
+
-d '{
|
|
233
|
+
"model": "gpt-4o-mini",
|
|
234
|
+
"messages": [
|
|
235
|
+
{"role": "system", "content": "You are helpful."},
|
|
236
|
+
{"role": "user", "content": "Say hi"}
|
|
237
|
+
],
|
|
238
|
+
"stream": false
|
|
239
|
+
}' \
|
|
240
|
+
http://127.0.0.1:8080/v1/chat/completions | jq .
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Embeddings:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
curl -s \
|
|
247
|
+
-H "Authorization: Bearer sk-demo-key" \
|
|
248
|
+
-H "Content-Type: application/json" \
|
|
249
|
+
-d '{"model": "text-embedding-3-small", "input": "hello"}' \
|
|
250
|
+
http://127.0.0.1:8080/v1/embeddings | jq .
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
Image generation (if supported by a configured backend):
|
|
254
|
+
|
|
255
|
+
```bash
|
|
256
|
+
curl -s \
|
|
257
|
+
-H "Authorization: Bearer sk-demo-key" \
|
|
258
|
+
-H "Content-Type: application/json" \
|
|
259
|
+
-d '{"model": "gpt-image-1", "prompt": "a lighthouse at dusk"}' \
|
|
260
|
+
http://127.0.0.1:8080/v1/images/generations | jq .
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Accounting and Persistence
|
|
264
|
+
|
|
265
|
+
mini-apigw estimates request cost using backend‑specific `cost` rates and tracked token
|
|
266
|
+
usage (this is work in progress), and aggregates totals per app. By default, accounting
|
|
267
|
+
is in‑memory. To persist usage to Postgres for reporting or bootstrapping daily totals,
|
|
268
|
+
set the `database` section in `daemon.json` and apply the schema in `sql/schema.sql`.
|
|
269
|
+
|
|
270
|
+
Notes:
|
|
271
|
+
|
|
272
|
+
- Postgres is optional. If configured, the gateway uses `psycopg`/`psycopg2` to
|
|
273
|
+
insert request rows asynchronously and reconstructs the current‑day state on startup.
|
|
274
|
+
- Cost limits (`apps[].cost_limit`) are enforced against the running totals; requests
|
|
275
|
+
beyond the limit are rejected with `403`.
|
|
276
|
+
|
|
277
|
+
## Tracing
|
|
278
|
+
|
|
279
|
+
Per‑app tracing can write JSONL events and capture images from generation responses.
|
|
280
|
+
Enable under `apps[].trace` with `file` and/or `image_dir`. You can include prompts,
|
|
281
|
+
responses (non‑streaming), and masked API keys with `include_prompts`, `include_response`,
|
|
282
|
+
and `include_keys`.
|
|
283
|
+
|
|
284
|
+
Trace files are append‑only JSONL; image files are written under `imagedir` with content‑based
|
|
285
|
+
extensions for base64 payloads, or a `.txt` containing the URL for URL‑based images.
|
|
286
|
+
|
|
287
|
+
## Admin and Stats
|
|
288
|
+
|
|
289
|
+
Admin endpoints are local‑only by default (IPv4/IPv6 loopback) and can be extended
|
|
290
|
+
with `admin.stats_networks` CIDR allow‑lists.
|
|
291
|
+
|
|
292
|
+
- `POST /admin/reload` — reload configuration files atomically
|
|
293
|
+
- `POST /admin/shutdown` — request a graceful stop
|
|
294
|
+
- `GET /stats/live` — in‑flight/queue stats per backend and sequence group
|
|
295
|
+
- `GET /stats/usage?app_id=<id>` — current usage snapshot (optionally filtered)
|
|
296
|
+
|
|
297
|
+
The `mini-apigw reload` and `stop` CLI commands call these endpoints using the admin
|
|
298
|
+
bind defined in `daemon.json`.
|
|
299
|
+
|
|
300
|
+
Note that at this moment this means any local user can control shutdown and reload! This
|
|
301
|
+
is __work in progress__
|
|
302
|
+
|
|
303
|
+
## Deployment Notes
|
|
304
|
+
|
|
305
|
+
The CLI embeds Uvicorn and supports IPv4/IPv6 hosts or a Unix domain socket. For production, consider:
|
|
306
|
+
|
|
307
|
+
- running behind a reverse proxy (TLS termination, headers)
|
|
308
|
+
- setting `logging.redact_prompts` to `true` unless needed
|
|
309
|
+
- using `sequence_group` and `concurrency` to match GPU/CPU constraints
|
|
310
|
+
|
|
311
|
+
## License
|
|
312
|
+
|
|
313
|
+
See `LICENSE.md` for the full text.
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
# mini-apigw — Minimal OpenAI‑compatible API Gateway
|
|
2
|
+
|
|
3
|
+
__WORK IN PROGRESS_
|
|
4
|
+
|
|
5
|
+
mini-apigw is a small edge gateway that presents an OpenAI‑compatible API surface and routes requests
|
|
6
|
+
to multiple LLM and image generation backends. It is designed for __simplicity__ and ease of control: you
|
|
7
|
+
configure backends and apps in JSON, set policies and cost limits per app, and the gateway handles
|
|
8
|
+
routing, scheduling, usage accounting, optional persistence, trace logging, and admin endpoints.
|
|
9
|
+
|
|
10
|
+
The main reason I developed this gateway was to handle multiple access to shared resources - I personally
|
|
11
|
+
utilize machines that run LLMs (ollama, vllm) as well as SDXL based image generation and other software. They
|
|
12
|
+
compete for GPU resources and since those tools are usually not developed to work together nicely and
|
|
13
|
+
arbitrate GPU usage the gateway offers serialization of requests inside _sequence groups_. All backends
|
|
14
|
+
in the same sequence group execute requests of all backends in the same group strictly in sequence so the
|
|
15
|
+
local backends can handle loading and unloading of competing backends.
|
|
16
|
+
|
|
17
|
+
Backends included out of the box:
|
|
18
|
+
|
|
19
|
+
* OpenAI
|
|
20
|
+
* Ollama
|
|
21
|
+
|
|
22
|
+
In development:
|
|
23
|
+
|
|
24
|
+
* Anthropic
|
|
25
|
+
* Fooocus
|
|
26
|
+
* vLLM
|
|
27
|
+
|
|
28
|
+
The gateway exposes the familiar `/v1` endpoints (`/chat/completions`, `/completions`,
|
|
29
|
+
`/embeddings`, `/images/generations`, and `/models`) and normalizes responses where
|
|
30
|
+
needed. This allows one to use the ```openai``` client library for any backend. Note that the gateway
|
|
31
|
+
does not implement the full OpenAI API - it just passes through the above mentioned
|
|
32
|
+
endpoints. In addition it uses local API keys. Those API keys are used to select the application
|
|
33
|
+
from ```apps.json``` configuration file.
|
|
34
|
+
|
|
35
|
+
## Installation
|
|
36
|
+
|
|
37
|
+
From PyPI (recommended):
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install mini-apigw
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
From source (editable):
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Configuration
|
|
50
|
+
|
|
51
|
+
The gateway reads three JSON files from a configuration directory. By default this
|
|
52
|
+
is `./config` (at the moment, this will be fixed soon, this will be a breaking change!),
|
|
53
|
+
or override with the environment variable `MINIAPIGW_CONFIG_DIR` or the CLI flag `--config-dir`.
|
|
54
|
+
|
|
55
|
+
Required files:
|
|
56
|
+
|
|
57
|
+
- `daemon.json`: service, logging, admin, timeouts, and optional Postgres settings
|
|
58
|
+
- `backends.json`: model providers, aliases, costs, and capabilities
|
|
59
|
+
- `apps.json`: application definitions, API keys, allow/deny policies, cost limits, and tracing
|
|
60
|
+
|
|
61
|
+
Example `daemon.json` (minimal, no persistent accounting log):
|
|
62
|
+
|
|
63
|
+
```json
|
|
64
|
+
{
|
|
65
|
+
"listen": { "host_v4": "0.0.0.0", "port": 8080 },
|
|
66
|
+
"admin": { "bind": ["127.0.0.1:8081", "[::1]:8081"], "stats_networks": ["127.0.0.1/32", "::1/128"] },
|
|
67
|
+
"logging": { "level": "INFO", "redact_prompts": true, "access_log": true },
|
|
68
|
+
"reload": { "enable_sighup": true },
|
|
69
|
+
"timeouts": { "default_connect_s": 60, "default_read_s": 600 },
|
|
70
|
+
"database": null
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
Example `backends.json` (mixed OpenAI + Ollama):
|
|
75
|
+
|
|
76
|
+
```json
|
|
77
|
+
{
|
|
78
|
+
"aliases": { "llama3.2": "llama3.2:latest" },
|
|
79
|
+
"sequence_groups": { "local_gpu_01": { "description": "Serialized work for local GPU tasks" } },
|
|
80
|
+
"backends": [
|
|
81
|
+
{
|
|
82
|
+
"type": "openai",
|
|
83
|
+
"name": "openai-primary",
|
|
84
|
+
"base_url": "https://api.openai.com/v1",
|
|
85
|
+
"api_key": "<openai_key>",
|
|
86
|
+
"concurrency": 4,
|
|
87
|
+
"supports": { "chat": ["gpt-4o-mini"], "embeddings": ["text-embedding-3-small"], "images": ["gpt-image-1", "dall-e-3"] },
|
|
88
|
+
"cost": { "currency": "usd", "unit": "1k_tokens", "models": { "gpt-4o-mini": {"prompt": 0.002, "completion": 0.004} } }
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
"type": "ollama",
|
|
92
|
+
"name": "ollama-local",
|
|
93
|
+
"base_url": "http://127.0.0.1:11434",
|
|
94
|
+
"sequence_group": "local_gpu_01",
|
|
95
|
+
"concurrency": 1,
|
|
96
|
+
"supports": { "chat": ["llama3.2:latest", "gpt-oss:120b"], "completions": ["llama3.2:latest"], "embeddings": ["nomic-embed-text"] },
|
|
97
|
+
"cost": {
|
|
98
|
+
"models": {
|
|
99
|
+
"llama3.2:latest": {"prompt": 0.0, "completion": 0.0},
|
|
100
|
+
"gpt-oss:120b": {"prompt": 0.001, "completion": 0.001 }
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Example `apps.json` (one app, you just declare them one after each other), the API keys are threatened
|
|
109
|
+
transparent, you can use any string as Bearer token, it just has to be unique; the app ID is used in filemanes
|
|
110
|
+
so you might not want to use special characters:
|
|
111
|
+
|
|
112
|
+
```json
|
|
113
|
+
{
|
|
114
|
+
"apps": [
|
|
115
|
+
{
|
|
116
|
+
"app_id": "demo",
|
|
117
|
+
"name": "Demo application",
|
|
118
|
+
"api_keys": [
|
|
119
|
+
"sk-example-key"
|
|
120
|
+
],
|
|
121
|
+
"policy": {
|
|
122
|
+
"allow": [ "gpt-4o-mini", "llama3.2" ],
|
|
123
|
+
"deny": []
|
|
124
|
+
},
|
|
125
|
+
"cost_limit": {
|
|
126
|
+
"period": "day",
|
|
127
|
+
"limit": 10.0
|
|
128
|
+
},
|
|
129
|
+
"trace": {
|
|
130
|
+
"file": "/var/log/llmgw/demo.jsonl",
|
|
131
|
+
"image_dir": "/var/log/llmgw/images/demo",
|
|
132
|
+
"include_prompts": true,
|
|
133
|
+
"include_response": true,
|
|
134
|
+
"include_keys": true
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
]
|
|
138
|
+
}
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
Notes:
|
|
142
|
+
|
|
143
|
+
- Backends declare capabilities via `supports` (globs allowed) and optional `aliases`.
|
|
144
|
+
Use `concurrency` and `sequence_group` to tune throughput/serialization. Costs
|
|
145
|
+
under `cost` are used to estimate per‑app spend. This may of course deviate from real
|
|
146
|
+
platform billing. It's just there to provide an estimate (and may be reworked later on).
|
|
147
|
+
Also billing is currently not tracked for images.
|
|
148
|
+
- Apps bind one or more API keys to an `app_id`, use `policy.allow`/`policy.deny` to
|
|
149
|
+
restrict models, and `cost_limit` to enforce soft limits. Per‑app traces can be persisted
|
|
150
|
+
as JSONL with optional image capture.
|
|
151
|
+
- The admin interface binds on the same port (due to limitations in FastAPI code and simplicity).
|
|
152
|
+
It is restricted to localhost by default, beware of limitations of this kind of filtering
|
|
153
|
+
when running local jails; additional networks can be allow‑listed with CIDRs in `admin.stats_networks`.
|
|
154
|
+
|
|
155
|
+
## Running
|
|
156
|
+
|
|
157
|
+
The package installs a console entry point `mini-apigw`.
|
|
158
|
+
|
|
159
|
+
Foreground server_
|
|
160
|
+
|
|
161
|
+
```bash
|
|
162
|
+
mini-apigw start --config-dir ./config --foreground --reload
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Daemonize with defaults from `daemon.json` (port/host or Unix socket):
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
mini-apigw start --config-dir ./config
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
Override listener explicitly:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
mini-apigw start --config-dir ./config --host 0.0.0.0 --port 8080
|
|
175
|
+
# or use a Unix domain socket
|
|
176
|
+
mini-apigw start --config-dir ./config --unix-socket /var/llmgw/llmgw.sock
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
Admin helpers (call built‑in admin endpoints):
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
mini-apigw reload --config-dir ./config
|
|
183
|
+
mini-apigw stop --config-dir ./config
|
|
184
|
+
mini-apigw token --bytes 32 # generate a random API key
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
## OpenAI‑Compatible API
|
|
188
|
+
|
|
189
|
+
All endpoints live under `/v1` and require `Authorization: Bearer <api_key>`.
|
|
190
|
+
|
|
191
|
+
List models (combines declared and auto‑discovered where available):
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
curl -s -H "Authorization: Bearer sk-demo-key" http://127.0.0.1:8080/v1/models | jq .
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
Chat completions (JSON response; set `stream: true` for SSE):
|
|
198
|
+
|
|
199
|
+
```bash
|
|
200
|
+
curl -s \
|
|
201
|
+
-H "Authorization: Bearer sk-demo-key" \
|
|
202
|
+
-H "Content-Type: application/json" \
|
|
203
|
+
-d '{
|
|
204
|
+
"model": "gpt-4o-mini",
|
|
205
|
+
"messages": [
|
|
206
|
+
{"role": "system", "content": "You are helpful."},
|
|
207
|
+
{"role": "user", "content": "Say hi"}
|
|
208
|
+
],
|
|
209
|
+
"stream": false
|
|
210
|
+
}' \
|
|
211
|
+
http://127.0.0.1:8080/v1/chat/completions | jq .
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
Embeddings:
|
|
215
|
+
|
|
216
|
+
```bash
|
|
217
|
+
curl -s \
|
|
218
|
+
-H "Authorization: Bearer sk-demo-key" \
|
|
219
|
+
-H "Content-Type: application/json" \
|
|
220
|
+
-d '{"model": "text-embedding-3-small", "input": "hello"}' \
|
|
221
|
+
http://127.0.0.1:8080/v1/embeddings | jq .
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Image generation (if supported by a configured backend):
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
curl -s \
|
|
228
|
+
-H "Authorization: Bearer sk-demo-key" \
|
|
229
|
+
-H "Content-Type: application/json" \
|
|
230
|
+
-d '{"model": "gpt-image-1", "prompt": "a lighthouse at dusk"}' \
|
|
231
|
+
http://127.0.0.1:8080/v1/images/generations | jq .
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
## Accounting and Persistence
|
|
235
|
+
|
|
236
|
+
mini-apigw estimates request cost using backend‑specific `cost` rates and tracked token
|
|
237
|
+
usage (this is work in progress), and aggregates totals per app. By default, accounting
|
|
238
|
+
is in‑memory. To persist usage to Postgres for reporting or bootstrapping daily totals,
|
|
239
|
+
set the `database` section in `daemon.json` and apply the schema in `sql/schema.sql`.
|
|
240
|
+
|
|
241
|
+
Notes:
|
|
242
|
+
|
|
243
|
+
- Postgres is optional. If configured, the gateway uses `psycopg`/`psycopg2` to
|
|
244
|
+
insert request rows asynchronously and reconstructs the current‑day state on startup.
|
|
245
|
+
- Cost limits (`apps[].cost_limit`) are enforced against the running totals; requests
|
|
246
|
+
beyond the limit are rejected with `403`.
|
|
247
|
+
|
|
248
|
+
## Tracing
|
|
249
|
+
|
|
250
|
+
Per‑app tracing can write JSONL events and capture images from generation responses.
|
|
251
|
+
Enable under `apps[].trace` with `file` and/or `image_dir`. You can include prompts,
|
|
252
|
+
responses (non‑streaming), and masked API keys with `include_prompts`, `include_response`,
|
|
253
|
+
and `include_keys`.
|
|
254
|
+
|
|
255
|
+
Trace files are append‑only JSONL; image files are written under `imagedir` with content‑based
|
|
256
|
+
extensions for base64 payloads, or a `.txt` containing the URL for URL‑based images.
|
|
257
|
+
|
|
258
|
+
## Admin and Stats
|
|
259
|
+
|
|
260
|
+
Admin endpoints are local‑only by default (IPv4/IPv6 loopback) and can be extended
|
|
261
|
+
with `admin.stats_networks` CIDR allow‑lists.
|
|
262
|
+
|
|
263
|
+
- `POST /admin/reload` — reload configuration files atomically
|
|
264
|
+
- `POST /admin/shutdown` — request a graceful stop
|
|
265
|
+
- `GET /stats/live` — in‑flight/queue stats per backend and sequence group
|
|
266
|
+
- `GET /stats/usage?app_id=<id>` — current usage snapshot (optionally filtered)
|
|
267
|
+
|
|
268
|
+
The `mini-apigw reload` and `stop` CLI commands call these endpoints using the admin
|
|
269
|
+
bind defined in `daemon.json`.
|
|
270
|
+
|
|
271
|
+
Note that at this moment this means any local user can control shutdown and reload! This
|
|
272
|
+
is __work in progress__
|
|
273
|
+
|
|
274
|
+
## Deployment Notes
|
|
275
|
+
|
|
276
|
+
The CLI embeds Uvicorn and supports IPv4/IPv6 hosts or a Unix domain socket. For production, consider:
|
|
277
|
+
|
|
278
|
+
- running behind a reverse proxy (TLS termination, headers)
|
|
279
|
+
- setting `logging.redact_prompts` to `true` unless needed
|
|
280
|
+
- using `sequence_group` and `concurrency` to match GPU/CPU constraints
|
|
281
|
+
|
|
282
|
+
## License
|
|
283
|
+
|
|
284
|
+
See `LICENSE.md` for the full text.
|