From 4b59383630536bbe1902129d8400bcbd13b642cf Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 10:35:17 +0800 Subject: [PATCH 01/12] =?UTF-8?q?=F0=9F=90=9B=20fixed=20cd=20kube=20config?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/helm-deploy/action.yml | 2 +- .github/workflows/ci.yml | 4 ++-- README.md | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/actions/helm-deploy/action.yml b/.github/actions/helm-deploy/action.yml index cba3c14..e8b702d 100644 --- a/.github/actions/helm-deploy/action.yml +++ b/.github/actions/helm-deploy/action.yml @@ -32,7 +32,7 @@ runs: shell: bash run: | mkdir -p $HOME/.kube - echo "${{ inputs.kube_config_data }}" | base64 -d > $HOME/.kube/config + echo "${{ inputs.kube_config_data }}" > $HOME/.kube/config chmod 600 $HOME/.kube/config - name: Install Helm diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e9eba8..a0e5a68 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -36,12 +36,12 @@ jobs: uv run pytest tests/ --ignore=tests/e2e/ --cov=slm_server --cov-report=xml --cov-report=term-missing - name: Upload coverage to Codecov - uses: codecov/codecov-action@v4 + uses: codecov/codecov-action@v5 with: file: ./coverage.xml flags: unittests - name: codecov-umbrella fail_ci_if_error: false + token: ${{ secrets.CODECOV_TOKEN }} - name: Lint with ruff run: | diff --git a/README.md b/README.md index 8794c44..b98e1b5 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ -# 🤖 SLM Server +# Small-Language-Model Server [![CI Pipeline](https://github.com/XyLearningProgramming/slm_server/actions/workflows/ci.yml/badge.svg)](https://github.com/XyLearningProgramming/slm_server/actions/workflows/ci.yml) [![codecov](https://codecov.io/gh/XyLearningProgramming/slm_server/branch/main/graph/badge.svg)](https://codecov.io/gh/XyLearningProgramming/slm_server) [![Docker](https://img.shields.io/badge/docker-ready-blue.svg)](https://hub.docker.com/r/x3huang/slm_server) [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) -> 🚀 **Production-ready FastAPI model server** for small language models with OpenAI-compatible API, built-in observability, and enterprise-grade deployment tools. +🚀 A light model server that serves small language models (default: `Qwen3-0.6B-GGUF`) using `llama-cpp` via the OpenAI-compatible `/chat/completions` API. Designed for resource-constrained environments with comprehensive monitoring and deployment automation. -A light model server that serves small language models (default: `Qwen3-0.6B-GGUF`) using `llama-cpp` via the OpenAI-compatible `/chat/completions` API. Designed for resource-constrained environments with comprehensive monitoring and deployment automation. +> This is still a WIP project. Issues, pull-requests are welcome. I mainly use this repo to deploy a SLM model as part of the backend on my own site [x3huang.dev](https://x3huang.dev/) while trying my best to keep this repo model-agonistic. ## ✨ Features From dc795ec2086e54be571373d67650658ff00f655c Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 10:48:07 +0800 Subject: [PATCH 02/12] =?UTF-8?q?=F0=9F=90=9B=20added=20echo=20masks?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/helm-deploy/action.yml | 48 ++++++++++++++++++-------- 1 file changed, 34 insertions(+), 14 deletions(-) diff --git a/.github/actions/helm-deploy/action.yml b/.github/actions/helm-deploy/action.yml index e8b702d..4387772 100644 --- a/.github/actions/helm-deploy/action.yml +++ b/.github/actions/helm-deploy/action.yml @@ -27,44 +27,61 @@ inputs: runs: using: 'composite' - steps: + steps: + - name: Install Helm + uses: azure/setup-helm@v3 + with: + version: 'latest' + - name: Set up Kubernetes config shell: bash run: | mkdir -p $HOME/.kube echo "${{ inputs.kube_config_data }}" > $HOME/.kube/config chmod 600 $HOME/.kube/config - - - name: Install Helm - uses: azure/setup-helm@v3 - with: - version: 'latest' - + - name: Parse environment variables id: parse_env shell: bash run: | if [ -n "${{ inputs.helm_values_env }}" ]; then - echo "helm_env_values<> $GITHUB_OUTPUT + # Create temporary file to avoid exposing secrets in logs + temp_file=$(mktemp) echo "${{ inputs.helm_values_env }}" | while IFS='=' read -r key value; do # Skip commented lines and empty lines if [[ "$key" =~ ^#.*$ ]] || [ -z "$key" ]; then continue fi if [ -n "$key" ] && [ -n "$value" ]; then - echo " $key: \"$value\"" + echo " $key: \"$value\"" >> "$temp_file" fi - done >> $GITHUB_OUTPUT + done + + # Output the parsed values without exposing them in logs + echo "helm_env_values<> $GITHUB_OUTPUT + cat "$temp_file" >> $GITHUB_OUTPUT echo "EOF" >> $GITHUB_OUTPUT + rm "$temp_file" else echo "helm_env_values=" >> $GITHUB_OUTPUT fi + - name: Add secret masks + shell: bash + run: | + echo "::add-mask::${{ inputs.helm_values_env }}" + echo "::add-mask::${{ inputs.kube_config_data }}" + echo "::add-mask::${{ inputs.helm_values_persistence_nodename }}" + echo "::add-mask::${{ steps.parse_env.outputs.helm_env_values }}" + - name: Deploy with Helm shell: bash run: | - # Create temporary values file - cat > /tmp/override-values.yaml << EOF + # Create temporary values file with restricted permissions + temp_values=$(mktemp) + chmod 600 "$temp_values" + + cat > "$temp_values" << EOF image: repository: ${{ inputs.registry_repository }} tag: "${{ inputs.image_tag }}" @@ -77,13 +94,16 @@ runs: ${{ steps.parse_env.outputs.helm_env_values }} EOF - # Deploy using Helm + # Deploy using Helm (values file won't be logged due to file redirection) helm upgrade --install slm-server ./deploy/helm \ --namespace ${{ inputs.namespace }} \ --create-namespace \ - --values /tmp/override-values.yaml \ + --values "$temp_values" \ --wait \ --timeout 10m + + # Clean up temporary file + rm "$temp_values" - name: Verify deployment shell: bash From d747dcc11722b9924d7b44d89259cba730581a2b Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 10:59:03 +0800 Subject: [PATCH 03/12] =?UTF-8?q?=F0=9F=90=9B=20added=20echo=20masks=20in?= =?UTF-8?q?=20parse=20step?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/helm-deploy/action.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/actions/helm-deploy/action.yml b/.github/actions/helm-deploy/action.yml index 4387772..e9bbb2b 100644 --- a/.github/actions/helm-deploy/action.yml +++ b/.github/actions/helm-deploy/action.yml @@ -32,6 +32,8 @@ runs: uses: azure/setup-helm@v3 with: version: 'latest' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Set up Kubernetes config shell: bash @@ -53,6 +55,7 @@ runs: continue fi if [ -n "$key" ] && [ -n "$value" ]; then + echo "::add-mask::$value" echo " $key: \"$value\"" >> "$temp_file" fi done @@ -65,15 +68,7 @@ runs: else echo "helm_env_values=" >> $GITHUB_OUTPUT fi - - - name: Add secret masks - shell: bash - run: | - echo "::add-mask::${{ inputs.helm_values_env }}" - echo "::add-mask::${{ inputs.kube_config_data }}" - echo "::add-mask::${{ inputs.helm_values_persistence_nodename }}" - echo "::add-mask::${{ steps.parse_env.outputs.helm_env_values }}" - + - name: Deploy with Helm shell: bash run: | From 0dfb0bfb320b05623210ebc5cfec6f38397dc7d1 Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 11:06:15 +0800 Subject: [PATCH 04/12] =?UTF-8?q?=F0=9F=90=9B=20added=20github=20token=20f?= =?UTF-8?q?or=20helm=20action?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/helm-deploy/action.yml | 5 ++++- .github/workflows/cd.yml | 1 + .github/workflows/deploy.yml | 1 + README.md | 2 +- 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/actions/helm-deploy/action.yml b/.github/actions/helm-deploy/action.yml index e9bbb2b..d3441a5 100644 --- a/.github/actions/helm-deploy/action.yml +++ b/.github/actions/helm-deploy/action.yml @@ -24,6 +24,9 @@ inputs: description: 'Kubernetes namespace' required: true default: 'backend' + github_token: + description: 'GitHub token for Helm installation' + required: true runs: using: 'composite' @@ -33,7 +36,7 @@ runs: with: version: 'latest' env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ inputs.github_token }} - name: Set up Kubernetes config shell: bash diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index 6ff5431..ad14bf0 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -76,3 +76,4 @@ jobs: helm_values_persistence_hostpath: ${{ secrets.HELM_VALUES_PERSISTENCE_HOSTPATH }} helm_values_persistence_nodename: ${{ secrets.HELM_VALUES_PERSISTENCE_NODENAME }} namespace: ${{ env.NAMESPACE }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 7330445..14a53e2 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -30,3 +30,4 @@ jobs: helm_values_persistence_hostpath: ${{ secrets.HELM_VALUES_PERSISTENCE_HOSTPATH }} helm_values_persistence_nodename: ${{ secrets.HELM_VALUES_PERSISTENCE_NODENAME }} namespace: ${{ env.NAMESPACE }} + github_token: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md index b98e1b5..0dbf2a5 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![Docker](https://img.shields.io/badge/docker-ready-blue.svg)](https://hub.docker.com/r/x3huang/slm_server) [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) -🚀 A light model server that serves small language models (default: `Qwen3-0.6B-GGUF`) using `llama-cpp` via the OpenAI-compatible `/chat/completions` API. Designed for resource-constrained environments with comprehensive monitoring and deployment automation. +🚀 A light model server that serves small language models (default: `Qwen3-0.6B-GGUF`) as a **thin wrapper** around `llama-cpp` exposing the OpenAI-compatible `/chat/completions` API. > This is still a WIP project. Issues, pull-requests are welcome. I mainly use this repo to deploy a SLM model as part of the backend on my own site [x3huang.dev](https://x3huang.dev/) while trying my best to keep this repo model-agonistic. From 62e30b03195c0b3e2060a2b16e1a0ccdb9d2a01d Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 11:21:40 +0800 Subject: [PATCH 05/12] =?UTF-8?q?=F0=9F=90=9B=20trying=20to=20fix=20remote?= =?UTF-8?q?=20kube=20addr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- docs/2025-05-06-1010-slm.excalidraw | 736 ++++++++++++++++++++++++++++ docs/20250712_slm_img1.jpg | Bin 0 -> 23491 bytes 3 files changed, 739 insertions(+), 1 deletion(-) create mode 100644 docs/2025-05-06-1010-slm.excalidraw create mode 100644 docs/20250712_slm_img1.jpg diff --git a/README.md b/README.md index 0dbf2a5..d537c75 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,14 @@ [![Docker](https://img.shields.io/badge/docker-ready-blue.svg)](https://hub.docker.com/r/x3huang/slm_server) [![License](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE) -🚀 A light model server that serves small language models (default: `Qwen3-0.6B-GGUF`) as a **thin wrapper** around `llama-cpp` exposing the OpenAI-compatible `/chat/completions` API. +🚀 A light model server that serves small language models (default: `Qwen3-0.6B-GGUF`) as a **thin wrapper** around `llama-cpp` exposing the OpenAI-compatible `/chat/completions` API. Core logic is just <100 lines under `./slm_server/app.py`! > This is still a WIP project. Issues, pull-requests are welcome. I mainly use this repo to deploy a SLM model as part of the backend on my own site [x3huang.dev](https://x3huang.dev/) while trying my best to keep this repo model-agonistic. ## ✨ Features +[Thin wrapper around llama cpp](./docs/20250712_slm_img1.jpg) + - 🔌 **OpenAI-compatible API** - Drop-in replacement with `/chat/completions` endpoint and streaming support - ⚡ **Llama.cpp integration** - High-performance inference optimized for limited CPU and memory resources - 📊 **Production observability** - Built-in logging, Prometheus metrics, and OpenTelemetry tracing (all configurable) diff --git a/docs/2025-05-06-1010-slm.excalidraw b/docs/2025-05-06-1010-slm.excalidraw new file mode 100644 index 0000000..3c4ad93 --- /dev/null +++ b/docs/2025-05-06-1010-slm.excalidraw @@ -0,0 +1,736 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "id": "d5KFGQtTqgmEfUQ2f9Nui", + "type": "image", + "x": 819.1111111111111, + "y": 338.55555555555554, + "width": 50, + "height": 50, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a0", + "roundness": null, + "seed": 1050387154, + "version": 153, + "versionNonce": 2093715790, + "isDeleted": false, + "boundElements": null, + "updated": 1753067882970, + "link": null, + "locked": false, + "status": "saved", + "fileId": "fe765207c7c2adca59a2173fbf9c725e4d5e8755", + "scale": [ + 1, + 1 + ], + "crop": null + }, + { + "id": "iWDIL1-MFEBmwZ9qkPOuu", + "type": "image", + "x": 887.3333333333333, + "y": 336.7777777777777, + "width": 58, + "height": 58, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a1", + "roundness": null, + "seed": 265819474, + "version": 114, + "versionNonce": 807942030, + "isDeleted": false, + "boundElements": null, + "updated": 1753067882970, + "link": null, + "locked": false, + "status": "saved", + "fileId": "0eb3377779b69ac16bde4fd0d2567e364b814c2a", + "scale": [ + 1, + 1 + ], + "crop": null + }, + { + "id": "f93nRt5J3nf1Q8Rup5ZtG", + "type": "image", + "x": 814.5555555555555, + "y": 220.66666666666674, + "width": 48, + "height": 48, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a2", + "roundness": null, + "seed": 483176014, + "version": 191, + "versionNonce": 362157518, + "isDeleted": false, + "boundElements": [ + { + "id": "DJgZwmqfHEsLX1iv1EqyA", + "type": "arrow" + }, + { + "id": "yBaZcwB1LF4TS-iScZHP0", + "type": "arrow" + } + ], + "updated": 1753067882970, + "link": null, + "locked": false, + "status": "saved", + "fileId": "f35a28da51b3598320e399118f525c8c132d8e45", + "scale": [ + 1, + 1 + ], + "crop": null + }, + { + "id": "Y_sGogfPepqR6CRfkbdcb", + "type": "image", + "x": 970.1111111111111, + "y": 344.0000000000001, + "width": 48, + "height": 48, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a3", + "roundness": null, + "seed": 2094341070, + "version": 155, + "versionNonce": 429981838, + "isDeleted": false, + "boundElements": null, + "updated": 1753067882970, + "link": null, + "locked": false, + "status": "saved", + "fileId": "92a85fbc42d60fcac05944e0037af7644c690d70", + "scale": [ + 1, + 1 + ], + "crop": null + }, + { + "id": "7zYKfz8LVzN-YZVTuPswr", + "type": "image", + "x": 564.6666666666667, + "y": 216.33333333333334, + "width": 50, + "height": 50, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a5", + "roundness": null, + "seed": 1484408462, + "version": 154, + "versionNonce": 1349612434, + "isDeleted": false, + "boundElements": [ + { + "id": "DJgZwmqfHEsLX1iv1EqyA", + "type": "arrow" + } + ], + "updated": 1753067792330, + "link": null, + "locked": false, + "status": "saved", + "fileId": "bc6b89be43245e2a153c036535ad8e9913543d77", + "scale": [ + 1, + 1 + ], + "crop": null + }, + { + "id": "0aCxLnwtDDtxnqRyN-XLQ", + "type": "image", + "x": 1042.444444444444, + "y": 224.11111111111111, + "width": 50, + "height": 50, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a6", + "roundness": null, + "seed": 2099508878, + "version": 274, + "versionNonce": 1718104402, + "isDeleted": false, + "boundElements": [ + { + "id": "yBaZcwB1LF4TS-iScZHP0", + "type": "arrow" + } + ], + "updated": 1753067888299, + "link": null, + "locked": false, + "status": "saved", + "fileId": "ca62941f7399e66463568aa8918322939c388b38", + "scale": [ + 1, + 1 + ], + "crop": null + }, + { + "id": "-fbJNM47uFdykrFvSeyZK", + "type": "text", + "x": 716.3333333333333, + "y": 119.66666666666666, + "width": 184.7999267578125, + "height": 35, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a7", + "roundness": null, + "seed": 2027839762, + "version": 81, + "versionNonce": 519133134, + "isDeleted": false, + "boundElements": null, + "updated": 1753067749346, + "link": null, + "locked": false, + "text": "Architecture", + "fontSize": 28, + "fontFamily": 8, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Architecture", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "DJgZwmqfHEsLX1iv1EqyA", + "type": "arrow", + "x": 624.111111111111, + "y": 244.0962194762647, + "width": 181.1111111111113, + "height": 0.5774534760355721, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9", + "roundness": { + "type": 2 + }, + "seed": 805840974, + "version": 115, + "versionNonce": 1177431054, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "YvRJPW3RMyvdjxCzLJwYK" + } + ], + "updated": 1753067882970, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 181.1111111111113, + 0.5774534760355721 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "7zYKfz8LVzN-YZVTuPswr", + "focus": 0.10560652395514694, + "gap": 9.444444444444343 + }, + "endBinding": { + "elementId": "f93nRt5J3nf1Q8Rup5ZtG", + "focus": -0.003494034616608133, + "gap": 9.333333333333258 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "YvRJPW3RMyvdjxCzLJwYK", + "type": "text", + "x": 634.6111111111112, + "y": 232.7779785303318, + "width": 99, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "a9V", + "roundness": null, + "seed": 591776078, + "version": 27, + "versionNonce": 1827609102, + "isDeleted": false, + "boundElements": null, + "updated": 1753067840333, + "link": null, + "locked": false, + "text": "/chat/...", + "fontSize": 20, + "fontFamily": 8, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "DJgZwmqfHEsLX1iv1EqyA", + "originalText": "/chat/...", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "Q71UemsDoPUZGUU7GmF7M", + "type": "text", + "x": 559.6666666666666, + "y": 285.2222222222223, + "width": 66, + "height": 25, + "angle": 0.008391411423452233, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aA", + "roundness": null, + "seed": 2093717522, + "version": 30, + "versionNonce": 664588306, + "isDeleted": false, + "boundElements": null, + "updated": 1753067791121, + "link": null, + "locked": false, + "text": "Client", + "fontSize": 20, + "fontFamily": 8, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Client", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "eHqg04c1unPSY-TnKNTyF", + "type": "text", + "x": 802.2220285799476, + "y": 279.9905966656282, + "width": 77, + "height": 25, + "angle": 0.008391411423452233, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aB", + "roundness": null, + "seed": 364518034, + "version": 116, + "versionNonce": 1682629454, + "isDeleted": false, + "boundElements": [], + "updated": 1753067882970, + "link": null, + "locked": false, + "text": "fastapi", + "fontSize": 20, + "fontFamily": 8, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "fastapi", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "ClsA_P3JNbttcvXpm6Tl5", + "type": "text", + "x": 840.7777777777778, + "y": 404.66666666666663, + "width": 176, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aC", + "roundness": null, + "seed": 868887438, + "version": 73, + "versionNonce": 1340950926, + "isDeleted": false, + "boundElements": null, + "updated": 1753067882970, + "link": null, + "locked": false, + "text": "Monitoring Stack", + "fontSize": 20, + "fontFamily": 8, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Monitoring Stack", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "yBaZcwB1LF4TS-iScZHP0", + "type": "arrow", + "x": 872.9999999999999, + "y": 244.68951023155142, + "width": 156.6666666666664, + "height": 1.2735691422958553, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aD", + "roundness": { + "type": 2 + }, + "seed": 655191246, + "version": 210, + "versionNonce": 683292434, + "isDeleted": false, + "boundElements": [ + { + "type": "text", + "id": "Z_b8UlxbEyarx5eKiCAqs" + } + ], + "updated": 1753067888300, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 156.6666666666664, + -1.2735691422958553 + ] + ], + "lastCommittedPoint": null, + "startBinding": { + "elementId": "f93nRt5J3nf1Q8Rup5ZtG", + "focus": 0.0041498785598879095, + "gap": 10.444444444444343 + }, + "endBinding": { + "elementId": "0aCxLnwtDDtxnqRyN-XLQ", + "focus": 0.23815775589217766, + "gap": 12.777777777777601 + }, + "startArrowhead": null, + "endArrowhead": "arrow", + "elbowed": false + }, + { + "id": "Z_b8UlxbEyarx5eKiCAqs", + "type": "text", + "x": 809.6666666666666, + "y": 233.1247267891514, + "width": 110, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aE", + "roundness": null, + "seed": 1900016654, + "version": 32, + "versionNonce": 1672004878, + "isDeleted": false, + "boundElements": null, + "updated": 1753067875591, + "link": null, + "locked": false, + "text": "llama...()", + "fontSize": 20, + "fontFamily": 8, + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "yBaZcwB1LF4TS-iScZHP0", + "originalText": "llama...()", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "yu01Z2_snG4HGUOe-RjlZ", + "type": "text", + "x": 1039.6666666666665, + "y": 284.66666666666663, + "width": 253, + "height": 25, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aG", + "roundness": null, + "seed": 1633567122, + "version": 47, + "versionNonce": 170291214, + "isDeleted": false, + "boundElements": null, + "updated": 1753067911951, + "link": null, + "locked": false, + "text": "llama-cpp + local model", + "fontSize": 20, + "fontFamily": 8, + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "llama-cpp + local model", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "id": "izB3JSpU79Wxs336BTdte", + "type": "rectangle", + "x": 776.3333333333333, + "y": 189.11111111111111, + "width": 545.5555555555559, + "height": 277.7777777777779, + "angle": 0, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aH", + "roundness": { + "type": 3 + }, + "seed": 1285985106, + "version": 255, + "versionNonce": 1667324878, + "isDeleted": false, + "boundElements": null, + "updated": 1753067933988, + "link": null, + "locked": false + }, + { + "id": "JEnNdpxXvZeQeeseWxlvh", + "type": "image", + "x": 1162.4444444444443, + "y": 224.1111111111111, + "width": 44.4444444444444, + "height": 44.4444444444444, + "angle": 0, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "groupIds": [], + "frameId": null, + "index": "aJ", + "roundness": null, + "seed": 225450126, + "version": 123, + "versionNonce": 765751182, + "isDeleted": false, + "boundElements": null, + "updated": 1753067970475, + "link": null, + "locked": false, + "status": "saved", + "fileId": "e848d67084586251f2c893c65804900d56af1f0d", + "scale": [ + 1, + 1 + ], + "crop": null + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff", + "lockedMultiSelections": {} + }, + "files": { + "fe765207c7c2adca59a2173fbf9c725e4d5e8755": { + "mimeType": "image/png", + "id": "fe765207c7c2adca59a2173fbf9c725e4d5e8755", + "dataURL": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAAxNJREFUaEPtmFnITVEUx39fksxTppLMU7wplKGMUSJkePCVQt49k3hRFK9IXuTFlKEQ4kWSlELmTCEiJTJk2v9at063e7999j332MfX2U93WGef9VvD3mutFtrJamknHPhA/uQEegZYDnxv1v6xQKT/WWAp8K0ZMGlBfHIhuiS9fAVYBHwO2aCWrE/Bykt9ciF6VPZ8CwwALhvMl5BNqmV9CuYJMha4CAwGrgILgU+NwsQE0buHApeA4c4rN4H5wIdGYGKDSOchBjMSuOW8Mxd4HwpTBBDpPNBgxgP3gNnAmxCYooBIZyX+BWAi8MBgXqWFKRKIdO4DnAcmAc+AWcDTNDBFA5HOvYBz7sKcDLwwmCc+mCKCSOeedvNPBV5amD1qC6aoINK5K3DKPKLLc46rz+7Ug4kJ4ouW6v/f2YFQ87n/CUQAdfWNARLqCcl7S6USpBGzZnim9EjFeF5LZLByyKNePcocMXOOcaX3EuCuq480UEiuQUAr0Bk4YjLJ/1VXqWdXT/IaOAh8reOm3D2yDDgKHALWJJSQcteB/vabBgwqAK/Z95nACaC3ff8B9GhjqhINZD+wDthhU5KtVggusAr3IdAXOODK9mNAN/NavbyJAtLROrwuVsn+sl5cyiic1ruicLf16+oG06woIGpdn1sfoV5cS2W4Po8DtttwbiOwNw3Fv7jZa+WImqIbVqmq29O67XJpgvUYu1xbO91GQGpxFYZamjwqzGqtKB4Zba3qY2CUaaXPI4BhlhdK/JV2WinMBF84kO7ARzuB1CDJmpokdrKjeA+gsFKIbQE2A9uKCCInaFYlq68AfgLH7TieAsyzvlzN0jTnvdVFAknGteJffbe6Oy15RBXEYuC0fT4MrHIe+W2e06UZNbQ0INhUlZ0Kl/t2428wkH3AyYRcB3eyrQVmAP1syLATUC5FSfaUp2dmsdxPrcwaptygBKkYymuJlBbNKubVo+xHspo48PnSI2WOBIZMWvEytMrQShsrgXJNC63A9+Ymnnkan5tmgRs3DBL4nnjivhIlnmaBb/4LcnPZM8mRV7cAAAAASUVORK5CYII=", + "created": 1753067502326, + "lastRetrieved": 1753067502326 + }, + "0eb3377779b69ac16bde4fd0d2567e364b814c2a": { + "mimeType": "image/png", + "id": "0eb3377779b69ac16bde4fd0d2567e364b814c2a", + "dataURL": "data:image/png;base64,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", + "created": 1753067506989, + "lastRetrieved": 1753067506989 + }, + "f35a28da51b3598320e399118f525c8c132d8e45": { + "mimeType": "image/png", + "id": "f35a28da51b3598320e399118f525c8c132d8e45", + "dataURL": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAABJFJREFUaEPtmX9o1HUYx1/P585tnQMDI8iIRVlBLhEyrLDdbdMNC0YUCoIUaegfkSy0u6OwhkZu01JJKLD1R38s6p9RWmzs9tMiSP+Jin4b/bEMqah0ebfdfZ68zZVtd/t+7r6TOdrn7/fzfN6v5/l+7vN8vyfM8SVz3D/zALPdwfkOjHdAhXhihcHUqXK3oLegLAFZiGgG+FXQIRVzwkBHuux4P01Ndia6578DOxPLTYB2kGWuhhTt0nRgEy9V/+Iak0/nD6DpaMicD30DXF+oEVE+zoSOr/bbCV8AwVhvvUU7CzU/oTei69LNa4qOz+bxBRCI925W1TZgyBqtw+o5gzkG3OEEJbrHNq95zkmbR+QLwER7GhEOgLTZlprHs3uYaO8uRHc7mjpoW2qfctTmlM0QwMUOjMiwCXLUuQNwxQAUW8R5AF+PkEvZ/z0nOdVjHdDB0kY0e5b+WUmQz1C7VyIjHdPtc6UCXLzgUYxslark68VdZPG+pUZ1J9jVIDcCC12qXoAmXwcuTXGOtFRKbfLHXHnzdiAQ7W1Q9C2EUAGGCpW6AGRHrb0SST7jDrCjp8IE+eIyVHySB3nZttTsyHEG/qtTPSWRkZudAUw8cQiV7YWWs2C98qRtrT2sAyXPgrwwbbwNXifVwz9P1uR8hEy0+1vELC3YUEEB+rvNZG5jf/0ZHSh7G3SDR/gDEk594A3Q2He1Kc38BnI5f6GGDfJwuqWmSz8qv5b06PdAuQdAo4RTh7wB4t0Ro6avoGK6iBULMgTaZZVm9tVmTaODZe2obvRMIbJbqpLPewIE4j0bVbMvKK5LvxbMixlNJ2hdexpEXSJVMQyU7kdwG+aUfRJJRT0BTCzxBMhhFxNAu/0rtZlX7k856hl/ZEYiqIkieqdrHGhcwiMtngCBaGKLiuS9+SYSKPTpVaaOpuq0fhhaQiYTA224MFrfAATcjbkq5VEJJ990AXhQRaadP1CsxVbSuvZL7VuwEiOdIItdrRSlM3aF3Df6qScAsd5lBv18uk3G3mdba+/VPsoIlHyFSkVRplyDlPOUpxbJSka9Ada/EzA3Lf7D4xY+Yltqt+pAaQPwrqsPH7oOCaceyhWf87denk70iJGaaTYcn2H6S7cjTPlt9mE0V2gaa++R6tGTzgAmltgG8ponwNQ5foa9kwbZJuHkG/kS575tY92LDCY7zOX73uM2RRaHkwR+wOonBPVAroN7adr843SsZ52qvodIMIcPF4CDEk65XVLFgY5FTT/vxHqqLgiaRFk16b1gjgB4VMZzjveurO8u+Zo4/2cAOoRQhwbOovYYwnJgDnXA0ibVqbHPjzpYsguV7OfHWQYYKNsC6jn4jR0F1Z9Q6gmaP7H6PlCJyh6JJGfv464OltyOSva+KG6pbJVI8khxweNRvg7xWGHd3menelTOosFbc72oFwLkH+AkIYZLXgV5xHlj1VOIeUzCyUHnmDxC3wATebV/wV1g1gOrMFqByjVAKSpnQc8gfAd6AhPo5PT5k7KB7J9/vteMAfh2UmSCeYAiCzdjYfMdmLFSFpnob5NY4UBlvFgCAAAAAElFTkSuQmCC", + "created": 1753067523854, + "lastRetrieved": 1753067523854 + }, + "92a85fbc42d60fcac05944e0037af7644c690d70": { + "mimeType": "image/png", + "id": "92a85fbc42d60fcac05944e0037af7644c690d70", + "dataURL": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAAAXNSR0IArs4c6QAAAqJJREFUaEPtWE1rU0EUPXcSXwqatlRE6caFtGBNBNGlinRRKNj6keQvVBCTCmqbRBehYFJEEF79ByKIefoHXIp2IVUQu3BjF11bbBfFNLy5kmClaK0zb8bW4rz1vffcc86dd+c9wi5/aJf3D0dgpx10DjgHDBVwI2QooHG6c8BYQsMC/68D2ULxlWQhn89UzxiKqJy+GeYPB0YnJpJeI34JIQ1ChH1gHADEIQYnSOALQlqAwGuCfFz3p+cu58svBYEDv3pWuYOfAm1gtghQrlCaZKYyCEmVZgh4hpg3Vn9QWYrohDVMyubL90G4odL4xhgJzHWueKdXkmsvdJ2wiUmZQmmZQJ26BL7HlwO/WtPNtYlJmUL5IwH9uk2045nng5laSneMbGJSrlA8CRmbksQDAPcSkadOhlcDv7ZX90DbxPztHsiNTXahI55mxjAQ5kCi7xdiEp+Ch9Uj6oS3joyCqbzIMvniCQEagaChkDEgiJtgGg/86hPdEVIlrIKpTGArUN0RUiWggmmFgI2GotZwBKIqZyvPOWBLyah1nANRlWvlVSoV8eHz2juQXAz86fNRam2LA62FRCRGATkkIY6uL8FUj/d0fqn5loHFwL878s8QuHi90h0PG2kQDYNl9m9eQ4wcyFy7fYpi4RSYBhjUS8AedRV5NdWTSO7oCJlei1P7E8d3dIRMPkwIXKr7tWl1xzaPNBuh8eI9YnFLtwlivPna8M4lEmuzJm+gFq4RgfYPgXz5piS+o/FZWvea8kr/wY5l0/GxQaAtfu5qZR/HmhdA4SCxSLPgw2DqYmYCiSVmXiDiWSHo0bFu773pwd3ouKkDutPTXl42lF8H3nYC2oz/kOAI2FZUt55zQFcx2/HOAduK6tZzDugqZjt+1zvwDXI4bTRIIOWKAAAAAElFTkSuQmCC", + "created": 1753067531166, + "lastRetrieved": 1753067531166 + }, + "bc6b89be43245e2a153c036535ad8e9913543d77": { + "mimeType": "image/png", + "id": "bc6b89be43245e2a153c036535ad8e9913543d77", + "dataURL": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAA/pJREFUaEPtmVmsjkcYx38HsZWI4EZE7FvsXPaioRWt1BpLLDeERIhdEdGiERQNrSIuuEEsscZSQRMXLotKJYhWKiUi0UiQam2df/ocOZzznXfemTmOnHyTnOTLe+Z55v9/5pl5limhhoySGsKDIpFKdrIuMAIYDvQBWtncP4GL7ttR4DDwb0pvSL0jo4G1QNsMkL8DXwAHU5FJRaQ2sA6Ya8AuATuAc8Bt+9YaGAhMAXrbtw3AIuBFLKFURL41Ek+BGcBO4FUBcLUcucnA90B9QGQWvA9E5E77nWX/Bj5xfxc8QX0InDEyo4BDnnIVTovdER3sa3Ym5DJypzxjKrAd+M0Zo1vMBRBLZCywF9CZ6A+8zMMCkJvpJusFjAEO5JR/PT2WiEiIzEzgh0AQs4BNwB5gQqCO6IB43QHoBHQ1FwvBIZe6CkhXlxAFkondkUdAI3fzNHY3z+NAEJKXHslLT9CIJVJ6xVa7nmoHYOaPNkiRSCpLptJT3JFUlkylJ3ZHHgJNXEBrCuh3yJDsXyav30EjlsgVV1P0APpamhICQrI/u0LsshViITqiA+JuYDyw0AXG9UEI/pf9BtjlksdJgTqiiXwKnHRkbliaEpI0KnvuCEjXj9VFRJWhcqT2wGIrc/Ngkcxq4KblWcGVYuwZEeiPgJ+AZ+6cDMth1cHubB0DZIwBwPk8Fnh7bgoi0rnS1d3LgOfuBlJavjUD1HTgO6COyX4VQyJF9lt2fTUR1tiHLAOV5lbquMi9okfWgnkX8E3+fOd5r18kUsBUvpb2nVfcEW8LVDCxBXDfHV416RpkKFIPTM255sCDmEVLZVOdEcUEXbltXFvouMudPs8AdwL4zAKhOjCnY8nEEFFzTr2oOUA/A/ILMAS4kwFMfWClI+q+aChp3Ggdy6AufQiRZsA062W1NCD3LGncDPzjad2GwDxgtrmYxO6639Kh7mMul8tDRDugiLzCahAtrGRxiy0svw8Z9azJpyy4uylQa0jNbQVML72+RAYB28q8e5y1ZwQ1oQt13fOSEhato878xyasJwkZTxl2pcOHyFLLh9Sn/dUWij6cGbjU1Vd909MMpVxueWUyWUQkrIRO6bWUrUrxKJNlXfu/DKc8TK6s5FJ53JJCspURGepelo4Y8InuhtnnCSD1tJHW8RcZvaPo/bHcKEREB1sFk+JCSMGUmsx8c7U/rGle7oouRETtfdXQt4DOVjSlBpdHn3ZDhm1ndb2wvTEKEZEbKdh97R5vvsyzYhXOLS3ehG2cLxE9hYm9Uo+qvqF8uQvLKXum6+BLRAHpA98V3vG8J/Ym4+VaqYJcVXEsdySy4khVAUmut0gkuUkjFdaYHfkP4Hm0M1SzmDQAAAAASUVORK5CYII=", + "created": 1753067711904, + "lastRetrieved": 1753067711904 + }, + "ca62941f7399e66463568aa8918322939c388b38": { + "mimeType": "image/png", + "id": "ca62941f7399e66463568aa8918322939c388b38", + "dataURL": "data:image/png;base64,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", + "created": 1753067735200, + "lastRetrieved": 1753067735200 + }, + "e848d67084586251f2c893c65804900d56af1f0d": { + "mimeType": "image/png", + "id": "e848d67084586251f2c893c65804900d56af1f0d", + "dataURL": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAYAAAAeP4ixAAAAAXNSR0IArs4c6QAAARFJREFUaEPtmeEJwkAMhV+X0BVEp9DBXUNxAHGLSpCCCGcul4Q76+uf/skl+ZKXXqATVvJMK+GABjIngl4AnAA8ImL0BJH8bwCOAO5emFoQzc6ax3unQ2C0BJeAml0ryBXADoC8pTPNMtMSzAbZAjgDOHhl1htE4m8iYEYAEVm6YUYBccOMBOKCGQ2kGWZEkCaYUUHMML1BLBep7Gb70oFfAhGGYr69QCydEFt1wyCItaROe3ZkKaBaCWela4+reXBGaksZZMeOcEaCpPTphtKitCit7xXgjHBGOCOckVcFuP0mzULJLT+//PwmSY7SorQoLd7sf3qzJ0nf7Nb9W8EcMelAM0hSPvFute03PmKSxyeLw3ozZCg7nQAAAABJRU5ErkJggg==", + "created": 1753067962323, + "lastRetrieved": 1753067962323 + } + } +} \ No newline at end of file diff --git a/docs/20250712_slm_img1.jpg b/docs/20250712_slm_img1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..df79bcba7ae694b51499e7b1449541ea1cc422c8 GIT binary patch literal 23491 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�cRfIm9!~V+o-XrFGS~L`D9?1`x#e|RU$ci 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 Date: Mon, 21 Jul 2025 11:25:31 +0800 Subject: [PATCH 06/12] =?UTF-8?q?=F0=9F=90=9B=20fixed=20template=20test=20?= =?UTF-8?q?issue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/helm/templates/tests/test-connection.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/deploy/helm/templates/tests/test-connection.yaml b/deploy/helm/templates/tests/test-connection.yaml index e2d9288..4c4b28d 100644 --- a/deploy/helm/templates/tests/test-connection.yaml +++ b/deploy/helm/templates/tests/test-connection.yaml @@ -1,9 +1,9 @@ apiVersion: v1 kind: Pod metadata: - name: "{{ include "slm_server.fullname" . }}-test-connection" + name: "{{ include "slm-server.fullname" . }}-test-connection" labels: - {{- include "slm_server.labels" . | nindent 4 }} + {{- include "slm-server.labels" . | nindent 4 }} annotations: "helm.sh/hook": test spec: @@ -11,5 +11,5 @@ spec: - name: wget image: busybox command: ['wget'] - args: ['{{ include "slm_server.fullname" . }}:{{ .Values.service.port }}'] + args: ['{{ include "slm-server.fullname" . }}:{{ .Values.service.port }}'] restartPolicy: Never From 153770981c21eb79e266f89963bb0eb84e18555c Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 11:27:50 +0800 Subject: [PATCH 07/12] =?UTF-8?q?=F0=9F=90=9B=20fixed=20hpa=20template=20i?= =?UTF-8?q?ssue?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/helm/values.yaml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index a9c461e..de966a7 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -50,6 +50,14 @@ ingress: hpa: enabled: false +# This section is for setting up autoscaling more information can be found here: https://kubernetes.io/docs/concepts/workloads/autoscaling/ +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + # Environment variables to inject into the container # Example configuration for SLM server settings env: {} From 4fb7bc7941035603bf09004de955cf34a5d7a99a Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 11:32:08 +0800 Subject: [PATCH 08/12] =?UTF-8?q?=F0=9F=90=9B=20fixed=20naming=20issue=20i?= =?UTF-8?q?n=20charts?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- deploy/helm/templates/NOTES.txt | 8 ++++---- deploy/helm/templates/hpa.yaml | 6 +++--- deploy/helm/templates/ingress.yaml | 6 +++--- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/README.md b/README.md index d537c75..fe2188e 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ## ✨ Features -[Thin wrapper around llama cpp](./docs/20250712_slm_img1.jpg) +![Thin wrapper around llama cpp](./docs/20250712_slm_img1.jpg) - 🔌 **OpenAI-compatible API** - Drop-in replacement with `/chat/completions` endpoint and streaming support - ⚡ **Llama.cpp integration** - High-performance inference optimized for limited CPU and memory resources diff --git a/deploy/helm/templates/NOTES.txt b/deploy/helm/templates/NOTES.txt index 51c093a..9f7bd66 100644 --- a/deploy/helm/templates/NOTES.txt +++ b/deploy/helm/templates/NOTES.txt @@ -6,16 +6,16 @@ {{- end }} {{- end }} {{- else if contains "NodePort" .Values.service.type }} - export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "slm_server.fullname" . }}) + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "slm-server.fullname" . }}) export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT {{- else if contains "LoadBalancer" .Values.service.type }} NOTE: It may take a few minutes for the LoadBalancer IP to be available. - You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "slm_server.fullname" . }}' - export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "slm_server.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + You can watch its status by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "slm-server.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "slm-server.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") echo http://$SERVICE_IP:{{ .Values.service.port }} {{- else if contains "ClusterIP" .Values.service.type }} - export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "slm_server.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "slm-server.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") echo "Visit http://127.0.0.1:8080 to use your application" kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT diff --git a/deploy/helm/templates/hpa.yaml b/deploy/helm/templates/hpa.yaml index 65ab5c3..1525c2f 100644 --- a/deploy/helm/templates/hpa.yaml +++ b/deploy/helm/templates/hpa.yaml @@ -2,14 +2,14 @@ apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: - name: {{ include "slm_server.fullname" . }} + name: {{ include "slm-server.fullname" . }} labels: - {{- include "slm_server.labels" . | nindent 4 }} + {{- include "slm-server.labels" . | nindent 4 }} spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment - name: {{ include "slm_server.fullname" . }} + name: {{ include "slm-server.fullname" . }} minReplicas: {{ .Values.autoscaling.minReplicas }} maxReplicas: {{ .Values.autoscaling.maxReplicas }} metrics: diff --git a/deploy/helm/templates/ingress.yaml b/deploy/helm/templates/ingress.yaml index d021348..6119712 100644 --- a/deploy/helm/templates/ingress.yaml +++ b/deploy/helm/templates/ingress.yaml @@ -2,9 +2,9 @@ apiVersion: networking.k8s.io/v1 kind: Ingress metadata: - name: {{ include "slm_server.fullname" . }} + name: {{ include "slm-server.fullname" . }} labels: - {{- include "slm_server.labels" . | nindent 4 }} + {{- include "slm-server.labels" . | nindent 4 }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} @@ -35,7 +35,7 @@ spec: {{- end }} backend: service: - name: {{ include "slm_server.fullname" $ }} + name: {{ include "slm-server.fullname" $ }} port: number: {{ $.Values.service.port }} {{- end }} From b3bb86c741ad8849d1a075574a5e82471a19427d Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 11:35:45 +0800 Subject: [PATCH 09/12] =?UTF-8?q?=F0=9F=90=9B=20requested=20smaller=20cpu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/helm/values.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index de966a7..03961a3 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -81,7 +81,7 @@ env: {} # See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ resources: limits: - cpu: 2.5 + cpu: 2 memory: 800Mi # requests: # cpu: 1 From 8b6474b556ef5ff2d25a61e2a133ef0f8adc530e Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 11:42:37 +0800 Subject: [PATCH 10/12] =?UTF-8?q?=F0=9F=90=9B=20fixed=20cancel?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/actions/helm-deploy/action.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/actions/helm-deploy/action.yml b/.github/actions/helm-deploy/action.yml index d3441a5..011f8d9 100644 --- a/.github/actions/helm-deploy/action.yml +++ b/.github/actions/helm-deploy/action.yml @@ -103,6 +103,13 @@ runs: # Clean up temporary file rm "$temp_values" + - name: Cleanup on cancellation + if: cancelled() + shell: bash + run: | + echo "Workflow cancelled, attempting helm rollback..." + helm rollback slm-server 0 -n ${{ inputs.namespace }} --wait --timeout 5m || true + - name: Verify deployment shell: bash run: | From 7d2ea93cfe68cf749c647178388276eb069ad7ac Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 12:25:59 +0800 Subject: [PATCH 11/12] =?UTF-8?q?=F0=9F=90=9B=20downsized=20cpu=20limit?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- deploy/helm/templates/pv.yaml | 2 +- deploy/helm/values.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/deploy/helm/templates/pv.yaml b/deploy/helm/templates/pv.yaml index 3176a64..6e2b2c5 100644 --- a/deploy/helm/templates/pv.yaml +++ b/deploy/helm/templates/pv.yaml @@ -10,7 +10,7 @@ spec: storage: {{ .Values.persistence.size }} accessModes: - {{ .Values.persistence.accessMode }} - hostPath: + local: path: {{ .Values.persistence.hostPath }} nodeAffinity: required: diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index 03961a3..1714d52 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -81,7 +81,7 @@ env: {} # See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ resources: limits: - cpu: 2 + cpu: 1750m memory: 800Mi # requests: # cpu: 1 From 3b07df738d54714893ca22d4b4f609f5aa17f29c Mon Sep 17 00:00:00 2001 From: XyLearningProgramming Date: Mon, 21 Jul 2025 12:42:09 +0800 Subject: [PATCH 12/12] =?UTF-8?q?=F0=9F=90=9B=20downsized=20cpu=20limit=20?= =?UTF-8?q?again?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- deploy/helm/values.yaml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index fe2188e..840a337 100644 --- a/README.md +++ b/README.md @@ -52,7 +52,7 @@ docker run -p 8000:8000 -v $(pwd)/models:/app/models slm_server ### Test the API ```bash -curl -X POST http://localhost:8000/chat/completions \ +curl -X POST http://localhost:8000/api/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen", diff --git a/deploy/helm/values.yaml b/deploy/helm/values.yaml index 1714d52..890f6e8 100644 --- a/deploy/helm/values.yaml +++ b/deploy/helm/values.yaml @@ -81,7 +81,7 @@ env: {} # See https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/ resources: limits: - cpu: 1750m + cpu: 1500m memory: 800Mi # requests: # cpu: 1 @@ -93,7 +93,7 @@ probes: enabled: true path: /health initialDelaySeconds: 10 - periodSeconds: 10 + periodSeconds: 70 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 3 @@ -101,7 +101,7 @@ probes: enabled: true path: /health initialDelaySeconds: 30 - periodSeconds: 30 + periodSeconds: 70 timeoutSeconds: 5 successThreshold: 1 failureThreshold: 3