4 Commits
1 .. 4

Author SHA1 Message Date
subochev 350ef663c0 feat: SGLANG_COMPAT — chat_template_kwargs.enable_thinking=false при -no-think (llama.cpp/sglang)
Build LLM Proxy / Build and push (release) Successful in 21s
2026-08-21 02:53:52 +03:00
Hermes Agent f45ed0d11c feat: тайминг-лог запросов ([llm-proxy] chat model=... status=... в Nms)
Build LLM Proxy / Build and push (release) Successful in 22s
2026-08-20 17:39:18 +03:00
Hermes Agent 8fc743860c fix: compose — external network bifrost_default вместо network_mode 2026-08-20 13:25:16 +03:00
Hermes Agent 0843db20db fix: Dockerfile COPY build/libs/llm-proxy.jar (CI-путь)
Build LLM Proxy / Build and push (release) Successful in 27s
2026-08-20 13:18:58 +03:00
3 changed files with 39 additions and 12 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
FROM docker.io/library/eclipse-temurin:17-jre-alpine
COPY llm-proxy.jar /app/llm-proxy.jar
COPY build/libs/llm-proxy.jar /app/llm-proxy.jar
WORKDIR /app
EXPOSE 8100
ENTRYPOINT ["java", "-jar", "/app/llm-proxy.jar"]
+8 -1
View File
@@ -3,15 +3,22 @@
# моделей с суффиксом "-no-think", проксирует на RouterAI. Ответ — как есть.
#
# Запуск на 76.179 (llm-router): podman-compose up -d
# Образ тянется из нашего реестра (zot): images.binom.pw/llm-proxy:<tag>
services:
llm-proxy:
image: images.binom.pw/llm-proxy:latest
container_name: llm-proxy
restart: unless-stopped
network_mode: bifrost_default
networks:
- bifrost
environment:
- PORT=8100
- UPSTREAM_URL=https://routerai.ru/api/v1
- ROUTER_API_KEY=__SET_FROM_CONFIG_DB__
- EXCLUDED_PROVIDERS=deepseek
- THINKING_MODELS=deepseek/deepseek-v4-flash-0731,deepseek/deepseek-v4-flash
networks:
bifrost:
external: true
name: bifrost_default
+30 -10
View File
@@ -103,6 +103,10 @@ private suspend fun handleChat(
return
}
val start = System.currentTimeMillis()
val model = try {
json.parseToJsonElement(patched).jsonObject["model"]?.jsonPrimitive?.content ?: "?"
} catch (e: Exception) { "?" }
val req = HttpRequest.newBuilder()
.uri(URI.create(upstream.trimEnd('/') + "/chat/completions"))
.header("Authorization", "Bearer $apiKey")
@@ -111,17 +115,27 @@ private suspend fun handleChat(
.build()
val stream = json.parseToJsonElement(raw).jsonObject["stream"]?.jsonPrimitive?.content == "true"
if (stream) {
// SSE-стрим: транслируем как есть, чанк за чанком.
val resp = http.send(req, HttpResponse.BodyHandlers.ofInputStream())
val ct = resp.headers().firstValue("content-type").orElse("text/event-stream")
call.respondOutputStream(ContentType.parse(ct), HttpStatusCode.fromValue(resp.statusCode())) {
resp.body().use { input -> input.copyTo(this, 8192) }
try {
if (stream) {
// SSE-стрим: транслируем как есть, чанк за чанком.
val resp = http.send(req, HttpResponse.BodyHandlers.ofInputStream())
val ct = resp.headers().firstValue("content-type").orElse("text/event-stream")
call.respondOutputStream(ContentType.parse(ct), HttpStatusCode.fromValue(resp.statusCode())) {
resp.body().use { input -> input.copyTo(this, 8192) }
}
println("[llm-proxy] chat model=$model status=${resp.statusCode()} в ${System.currentTimeMillis() - start}ms stream=true")
} else {
val resp = http.send(req, HttpResponse.BodyHandlers.ofByteArray())
val ct = resp.headers().firstValue("content-type").orElse("application/json")
call.respondBytes(resp.body(), ContentType.parse(ct), HttpStatusCode.fromValue(resp.statusCode()))
println("[llm-proxy] chat model=$model status=${resp.statusCode()} в ${System.currentTimeMillis() - start}ms stream=false")
}
} else {
val resp = http.send(req, HttpResponse.BodyHandlers.ofByteArray())
val ct = resp.headers().firstValue("content-type").orElse("application/json")
call.respondBytes(resp.body(), ContentType.parse(ct), HttpStatusCode.fromValue(resp.statusCode()))
} catch (e: Exception) {
println("[llm-proxy] chat model=$model ОШИБКА: ${e.message} в ${System.currentTimeMillis() - start}ms")
call.respondBytes(
"""{"error":{"message":"upstream: ${e.message}"}}""".toByteArray(),
ContentType.Application.Json, HttpStatusCode.BadGateway,
)
}
}
@@ -141,6 +155,12 @@ internal fun patchBody(raw: String, excluded: List<String>, thinking: List<Strin
if (root["reasoning"] == null) {
root["reasoning"] = JsonObject(mapOf("enabled" to JsonPrimitive(false)))
}
// SGLANG_COMPAT: Qwen3.8 глушится только через chat_template_kwargs.enable_thinking=false
if (System.getenv("SGLANG_COMPAT") == "true") {
val ctk = root["chat_template_kwargs"]?.jsonObject?.toMutableMap() ?: mutableMapOf()
ctk["enable_thinking"] = JsonPrimitive(false)
root["chat_template_kwargs"] = JsonObject(ctk)
}
}
val provider = root["provider"]?.jsonObject?.toMutableMap() ?: mutableMapOf()