Spaces:
Running
Running
github-actions[bot] commited on
Commit ·
f6dd1f9
1
Parent(s): baeebee
deploy: 7ae99ac — 更新 index.js
Browse files- backend/src/index.js +14 -33
backend/src/index.js
CHANGED
|
@@ -15,7 +15,12 @@ const litellm = require("./litellm");
|
|
| 15 |
const app = express();
|
| 16 |
const PORT = parseInt(process.env.PORT || "3001", 10);
|
| 17 |
|
| 18 |
-
//
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
app.use(helmet({ crossOriginResourcePolicy: false }));
|
| 21 |
app.use(
|
|
@@ -33,7 +38,6 @@ app.use(
|
|
| 33 |
})
|
| 34 |
);
|
| 35 |
|
| 36 |
-
// Rate limiting
|
| 37 |
app.use(
|
| 38 |
"/api/",
|
| 39 |
rateLimit({
|
|
@@ -45,44 +49,28 @@ app.use(
|
|
| 45 |
})
|
| 46 |
);
|
| 47 |
|
| 48 |
-
// ─── Routes ───────────────────────────────────────────────────────────────
|
| 49 |
-
|
| 50 |
-
// BUG FIX: The simple stub was removed. statsRouter's /health checks LiteLLM + DB health.
|
| 51 |
app.use("/api/models", modelsRouter);
|
| 52 |
app.use("/api", statsRouter);
|
| 53 |
|
| 54 |
-
// 404
|
| 55 |
app.use((req, res) => {
|
| 56 |
res.status(404).json({ success: false, error: "Not found" });
|
| 57 |
});
|
| 58 |
|
| 59 |
-
// Error handler
|
| 60 |
app.use((err, req, res, _next) => {
|
| 61 |
logger.error("Unhandled error", { error: err.message, stack: err.stack });
|
| 62 |
res.status(500).json({ success: false, error: "Internal server error" });
|
| 63 |
});
|
| 64 |
|
| 65 |
-
// ─── Startup ──────────────────────────────────────────────────────────────
|
| 66 |
-
|
| 67 |
async function start() {
|
| 68 |
-
// Initialize DB
|
| 69 |
db.getDb();
|
| 70 |
logger.info("Database initialized");
|
| 71 |
-
|
| 72 |
-
// BUG FIX: 5s hardcoded delay was unreliable — LiteLLM can take 30-60s to start.
|
| 73 |
-
// Use retry loop with backoff instead.
|
| 74 |
syncModelsToLitellmWithRetry();
|
| 75 |
-
|
| 76 |
app.listen(PORT, "0.0.0.0", () => {
|
| 77 |
-
logger.info(
|
| 78 |
-
logger.info(
|
| 79 |
});
|
| 80 |
}
|
| 81 |
|
| 82 |
-
/**
|
| 83 |
-
* Retry wrapper: attempts sync with exponential backoff for up to ~5 minutes.
|
| 84 |
-
* Handles the case where LiteLLM container starts slower than the backend.
|
| 85 |
-
*/
|
| 86 |
async function syncModelsToLitellmWithRetry() {
|
| 87 |
const MAX_ATTEMPTS = 10;
|
| 88 |
const BASE_DELAY_MS = 5000;
|
|
@@ -93,33 +81,26 @@ async function syncModelsToLitellmWithRetry() {
|
|
| 93 |
return;
|
| 94 |
} catch (err) {
|
| 95 |
const delay = Math.min(BASE_DELAY_MS * attempt, 30000);
|
| 96 |
-
logger.warn(
|
| 97 |
await new Promise((r) => setTimeout(r, delay));
|
| 98 |
}
|
| 99 |
}
|
| 100 |
-
logger.error("LiteLLM did not become ready after all retry attempts.
|
| 101 |
}
|
| 102 |
|
| 103 |
-
/**
|
| 104 |
-
* On startup, re-register all persisted models with LiteLLM.
|
| 105 |
-
* This handles the case where LiteLLM was restarted and lost its in-memory state.
|
| 106 |
-
*/
|
| 107 |
async function syncModelsToLitellm() {
|
| 108 |
try {
|
| 109 |
const models = db.listModels({ enabledOnly: true });
|
| 110 |
-
logger.info(
|
| 111 |
-
|
| 112 |
for (const model of models) {
|
| 113 |
try {
|
| 114 |
-
// _apiKey is the raw (unmasked) API key set by deserializeModel
|
| 115 |
const litellmId = await litellm.registerModel({ ...model, _apiKey: model._apiKey });
|
| 116 |
db.updateModel(model.id, { litellmId });
|
| 117 |
-
logger.info(
|
| 118 |
} catch (err) {
|
| 119 |
-
logger.warn(
|
| 120 |
}
|
| 121 |
}
|
| 122 |
-
|
| 123 |
logger.info("Model sync complete");
|
| 124 |
} catch (err) {
|
| 125 |
logger.error("Model sync failed", { error: err.message });
|
|
@@ -129,4 +110,4 @@ async function syncModelsToLitellm() {
|
|
| 129 |
start().catch((err) => {
|
| 130 |
logger.error("Fatal startup error", { error: err.message });
|
| 131 |
process.exit(1);
|
| 132 |
-
});
|
|
|
|
| 15 |
const app = express();
|
| 16 |
const PORT = parseInt(process.env.PORT || "3001", 10);
|
| 17 |
|
| 18 |
+
// BUG FIX: Set trust proxy BEFORE rate limiter.
|
| 19 |
+
// nginx runs in front of backend and sets X-Forwarded-For header.
|
| 20 |
+
// Without trust proxy, express-rate-limit throws ERR_ERL_UNEXPECTED_X_FORWARDED_FOR
|
| 21 |
+
// and logs a ValidationError on every single request.
|
| 22 |
+
// Value 1 = trust first proxy hop (nginx on localhost).
|
| 23 |
+
app.set("trust proxy", 1);
|
| 24 |
|
| 25 |
app.use(helmet({ crossOriginResourcePolicy: false }));
|
| 26 |
app.use(
|
|
|
|
| 38 |
})
|
| 39 |
);
|
| 40 |
|
|
|
|
| 41 |
app.use(
|
| 42 |
"/api/",
|
| 43 |
rateLimit({
|
|
|
|
| 49 |
})
|
| 50 |
);
|
| 51 |
|
|
|
|
|
|
|
|
|
|
| 52 |
app.use("/api/models", modelsRouter);
|
| 53 |
app.use("/api", statsRouter);
|
| 54 |
|
|
|
|
| 55 |
app.use((req, res) => {
|
| 56 |
res.status(404).json({ success: false, error: "Not found" });
|
| 57 |
});
|
| 58 |
|
|
|
|
| 59 |
app.use((err, req, res, _next) => {
|
| 60 |
logger.error("Unhandled error", { error: err.message, stack: err.stack });
|
| 61 |
res.status(500).json({ success: false, error: "Internal server error" });
|
| 62 |
});
|
| 63 |
|
|
|
|
|
|
|
| 64 |
async function start() {
|
|
|
|
| 65 |
db.getDb();
|
| 66 |
logger.info("Database initialized");
|
|
|
|
|
|
|
|
|
|
| 67 |
syncModelsToLitellmWithRetry();
|
|
|
|
| 68 |
app.listen(PORT, "0.0.0.0", () => {
|
| 69 |
+
logger.info("AI Gateway Backend running on port " + PORT);
|
| 70 |
+
logger.info("Gateway public URL: " + (process.env.GATEWAY_PUBLIC_URL || "http://localhost"));
|
| 71 |
});
|
| 72 |
}
|
| 73 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 74 |
async function syncModelsToLitellmWithRetry() {
|
| 75 |
const MAX_ATTEMPTS = 10;
|
| 76 |
const BASE_DELAY_MS = 5000;
|
|
|
|
| 81 |
return;
|
| 82 |
} catch (err) {
|
| 83 |
const delay = Math.min(BASE_DELAY_MS * attempt, 30000);
|
| 84 |
+
logger.warn("LiteLLM not ready (attempt " + attempt + "/" + MAX_ATTEMPTS + "), retrying in " + delay + "ms...");
|
| 85 |
await new Promise((r) => setTimeout(r, delay));
|
| 86 |
}
|
| 87 |
}
|
| 88 |
+
logger.error("LiteLLM did not become ready after all retry attempts.");
|
| 89 |
}
|
| 90 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 91 |
async function syncModelsToLitellm() {
|
| 92 |
try {
|
| 93 |
const models = db.listModels({ enabledOnly: true });
|
| 94 |
+
logger.info("Syncing " + models.length + " models to LiteLLM...");
|
|
|
|
| 95 |
for (const model of models) {
|
| 96 |
try {
|
|
|
|
| 97 |
const litellmId = await litellm.registerModel({ ...model, _apiKey: model._apiKey });
|
| 98 |
db.updateModel(model.id, { litellmId });
|
| 99 |
+
logger.info("Synced: " + model.name);
|
| 100 |
} catch (err) {
|
| 101 |
+
logger.warn("Failed to sync model " + model.name + ": " + err.message);
|
| 102 |
}
|
| 103 |
}
|
|
|
|
| 104 |
logger.info("Model sync complete");
|
| 105 |
} catch (err) {
|
| 106 |
logger.error("Model sync failed", { error: err.message });
|
|
|
|
| 110 |
start().catch((err) => {
|
| 111 |
logger.error("Fatal startup error", { error: err.message });
|
| 112 |
process.exit(1);
|
| 113 |
+
});
|