openvibers/openvibe.ai Commits
AI: the public vhost proxies /v1/chat/completions (POST, unbuffered) and /v1/models (GET) (#71)
Changes
@@@ -88,6 +88,34 @@ server {proxy_set_header X-Forwarded-Proto $scheme;}++ # ── OpenAI-compatible (the same app tokens): Chat Completions and the model list ──++ location = /v1/chat/completions {++ limit_req zone=ovai_apps burst=20 nodelay;++ limit_req_status 429;++ limit_except POST { deny all; }++ proxy_pass http://127.0.0.1:4700;++ proxy_http_version 1.1;++ proxy_set_header Host $host;++ proxy_set_header X-Real-IP $remote_addr;++ proxy_set_header X-Forwarded-For $remote_addr;++ proxy_set_header CF-Connecting-IP $remote_addr;++ proxy_set_header X-Forwarded-Proto $scheme;++ proxy_buffering off; # stream: true answers server-sent events++ proxy_read_timeout 120s;++ }++ location = /v1/models {++ limit_req zone=ovai_apps burst=20 nodelay;++ limit_req_status 429;++ limit_except GET { deny all; }++ proxy_pass http://127.0.0.1:4700;++ proxy_http_version 1.1;++ proxy_set_header Host $host;++ proxy_set_header X-Real-IP $remote_addr;++ proxy_set_header X-Forwarded-For $remote_addr;++ proxy_set_header CF-Connecting-IP $remote_addr;++ proxy_set_header X-Forwarded-Proto $scheme;++ }++# ── Service API: host-local only (see header) ───────────location /api/ {return 403;@@@ -87,8 +87,12 @@ t.test('the public vhost proxies the home, its files and exactly the developer-afor (const loc of ['location = / {', 'location = /stats', 'location = /sitemap.xml', 'location = /llms.txt', 'location /shared/']) assert.ok(conf.includes(loc), loc);const ops = conf.indexOf('location ~ ^/api/v1/(chat|generate|summarize|classify|extract|embed)$ {');const run = conf.indexOf('location ~ ^/api/v1/runs/[A-Za-z0-9_-]+$ {');++ const completions = conf.indexOf('location = /v1/chat/completions {');++ const models = conf.indexOf('location = /v1/models {');assert.ok(ops >= 0 && run >= 0, 'the six operations and the run read');-- for (const [i, verb] of [[ops, 'POST'], [run, 'GET']]) {++ assert.ok(completions >= 0 && models >= 0, 'the OpenAI-compatible endpoint and its model list');++ assert.ok(conf.slice(completions, conf.indexOf('\n }', completions)).includes('proxy_buffering off;'), 'streamed answers are not buffered');++ for (const [i, verb] of [[ops, 'POST'], [run, 'GET'], [completions, 'POST'], [models, 'GET']]) {const block = conf.slice(i, conf.indexOf('\n }', i));assert.ok(block.includes(`limit_except ${verb} { deny all; }`), verb);assert.ok(block.includes('limit_req zone=ovai_apps'), 'rate-limited per address');
@@ -88,6 +88,34 @@ server {
@@ -88,6 +88,34 @@ server {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
+ # ── OpenAI-compatible (the same app tokens): Chat Completions and the model list ──
+ location = /v1/chat/completions {
+ limit_req zone=ovai_apps burst=20 nodelay;
+ limit_req_status 429;
+ limit_except POST { deny all; }
+ proxy_pass http://127.0.0.1:4700;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $remote_addr;
+ proxy_set_header CF-Connecting-IP $remote_addr;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_buffering off; # stream: true answers server-sent events
+ proxy_read_timeout 120s;
+ }
+ location = /v1/models {
+ limit_req zone=ovai_apps burst=20 nodelay;
+ limit_req_status 429;
+ limit_except GET { deny all; }
+ proxy_pass http://127.0.0.1:4700;
+ proxy_http_version 1.1;
+ proxy_set_header Host $host;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_set_header X-Forwarded-For $remote_addr;
+ proxy_set_header CF-Connecting-IP $remote_addr;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ }
+
# ── Service API: host-local only (see header) ───────────
# ── Service API: host-local only (see header) ───────────
location /api/ {
location /api/ {
return 403;
return 403;
@@ -87,8 +87,12 @@ t.test('the public vhost proxies the home, its files and exactly the developer-a
@@ -87,8 +87,12 @@ t.test('the public vhost proxies the home, its files and exactly the developer-a
for (const loc of ['location = / {', 'location = /stats', 'location = /sitemap.xml', 'location = /llms.txt', 'location /shared/']) assert.ok(conf.includes(loc), loc);
for (const loc of ['location = / {', 'location = /stats', 'location = /sitemap.xml', 'location = /llms.txt', 'location /shared/']) assert.ok(conf.includes(loc), loc);
const ops = conf.indexOf('location ~ ^/api/v1/(chat|generate|summarize|classify|extract|embed)$ {');
const ops = conf.indexOf('location ~ ^/api/v1/(chat|generate|summarize|classify|extract|embed)$ {');
const run = conf.indexOf('location ~ ^/api/v1/runs/[A-Za-z0-9_-]+$ {');
const run = conf.indexOf('location ~ ^/api/v1/runs/[A-Za-z0-9_-]+$ {');
+ const completions = conf.indexOf('location = /v1/chat/completions {');
+ const models = conf.indexOf('location = /v1/models {');
assert.ok(ops >= 0 && run >= 0, 'the six operations and the run read');
assert.ok(ops >= 0 && run >= 0, 'the six operations and the run read');
- for (const [i, verb] of [[ops, 'POST'], [run, 'GET']]) {
+ assert.ok(completions >= 0 && models >= 0, 'the OpenAI-compatible endpoint and its model list');
+ assert.ok(conf.slice(completions, conf.indexOf('\n }', completions)).includes('proxy_buffering off;'), 'streamed answers are not buffered');
+ for (const [i, verb] of [[ops, 'POST'], [run, 'GET'], [completions, 'POST'], [models, 'GET']]) {
const block = conf.slice(i, conf.indexOf('\n }', i));
const block = conf.slice(i, conf.indexOf('\n }', i));
assert.ok(block.includes(`limit_except ${verb} { deny all; }`), verb);
assert.ok(block.includes(`limit_except ${verb} { deny all; }`), verb);
assert.ok(block.includes('limit_req zone=ovai_apps'), 'rate-limited per address');
assert.ok(block.includes('limit_req zone=ovai_apps'), 'rate-limited per address');