{"id":31842772,"url":"https://github.com/ciderapp/saki-ml","last_synced_at":"2025-10-12T06:53:18.796Z","repository":{"id":312959014,"uuid":"1040949409","full_name":"ciderapp/Saki-ML","owner":"ciderapp","description":"ML Subsystem for Saki, focuses on Anti-Spam and moderation techniques alongside LLM subprocesses.","archived":false,"fork":false,"pushed_at":"2025-09-03T02:27:05.000Z","size":32509,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-01T00:32:14.008Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ciderapp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"github":"ciderapp","ko_fi":"cryptofyre","open_collective":"ciderapp","custom":"https://opencollective.com/ciderapp/donate/10/month/Support%20Development?redirect=https://cidercollective.dev/thanks"}},"created_at":"2025-08-19T18:45:33.000Z","updated_at":"2025-09-03T02:27:09.000Z","dependencies_parsed_at":"2025-09-03T04:33:39.019Z","dependency_job_id":null,"html_url":"https://github.com/ciderapp/Saki-ML","commit_stats":null,"previous_names":["ciderapp/saki-ml"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ciderapp/Saki-ML","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ciderapp%2FSaki-ML","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ciderapp%2FSaki-ML/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ciderapp%2FSaki-ML/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ciderapp%2FSaki-ML/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ciderapp","download_url":"https://codeload.github.com/ciderapp/Saki-ML/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ciderapp%2FSaki-ML/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":279010542,"owners_count":26084758,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-10-12T02:00:06.719Z","response_time":53,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-12T06:53:17.802Z","updated_at":"2025-10-12T06:53:18.790Z","avatar_url":"https://github.com/ciderapp.png","language":"C#","funding_links":["https://github.com/sponsors/ciderapp","https://ko-fi.com/cryptofyre","https://opencollective.com/ciderapp","https://opencollective.com/ciderapp/donate/10/month/Support%20Development?redirect=https://cidercollective.dev/thanks"],"categories":[],"sub_categories":[],"readme":"# 🌸💽 Saki-ML\n\nSpam classification microservice for Saki using ML.NET. It exposes a fast HTTP API to classify messages (ham/spam), backed by a background queue and a safe single prediction worker. Ships as a single-file, self-contained binary and Docker images (Debian-slim and Alpine).\n\n## Features\n- ML.NET text classification (ham/spam) using the generated `SpamClassifier`\n- Background queue with a single worker for thread-safe inference\n- Simple API key header authentication\n- Minimal HTTP API: `/classify` and `/health`\n- Analytics endpoints: `/analytics`, `/analytics/last/{minutes}`\n- Unsure insights: `/insights/unsure`, `/insights/unsure/last/{minutes}`\n- Configuration diagnostics: `/config/diagnostics`\n- Single-file, self-contained builds with ReadyToRun\n- Docker images for Debian-slim (recommended) and Alpine (experimental)\n\n## Requirements\n- For local runs: .NET 9 SDK (to build/publish) or use Docker\n- Port 8080 available (configurable via `ASPNETCORE_URLS`)\n\n## Quick start (Docker)\n### Debian-slim (recommended)\n```bash\ndocker build -t saki-ml:latest -f Dockerfile .\ndocker run --rm -p 8080:8080 -e SAKI_ML_API_KEY=your-strong-key saki-ml:latest\n```\n\n### Alpine (experimental)\n```bash\ndocker build -t saki-ml:alpine -f Dockerfile.alpine .\ndocker run --rm -p 8080:8080 -e SAKI_ML_API_KEY=your-strong-key saki-ml:alpine\n```\n\n## Quick start (single-file binary)\nPublish and run the self-contained executable (no .NET runtime required on target):\n\nLinux (glibc):\n```bash\ndotnet publish Saki-ML.csproj -c Release -r linux-x64 --self-contained true -o out/linux-x64\nchmod +x out/linux-x64/Saki-ML\nSAKI_ML_API_KEY=your-strong-key ./out/linux-x64/Saki-ML\n```\n\nLinux (Alpine/musl):\n```bash\ndotnet publish Saki-ML.csproj -c Release -r linux-musl-x64 --self-contained true -o out/linux-musl-x64\nchmod +x out/linux-musl-x64/Saki-ML\nSAKI_ML_API_KEY=your-strong-key ./out/linux-musl-x64/Saki-ML\n```\n\nWindows:\n```powershell\ndotnet publish Saki-ML.csproj -c Release -r win-x64 --self-contained true -o out/win-x64\nsetx SAKI_ML_API_KEY your-strong-key\nout\\win-x64\\Saki-ML.exe\n```\n\n## Configuration\n- `SAKI_ML_API_KEY` (required in production): API key for requests\n- `QueueCapacity` (optional, default 1000): bounded queue size\n- `ASPNETCORE_URLS` (optional): host/port binding, default `http://0.0.0.0:8080`\n- `UnsureThreshold` (optional, default 0.75): if top confidence is below this, verdict is `Unsure`\n- `BlockThreshold` (optional, default 0.85): minimum confidence to auto-block when label is `spam`\n\n## API\n### Health\n- `GET /health`\n- Response: `{ \"status\": \"ok\" }`\n\n### Classify\n- `POST /classify`\n- Headers: `x-api-key: your-strong-key`\n- Body:\n```json\n{ \"Text\": \"win an instant discord nitro giveaway, click here!\" }\n```\n- Response:\n```json\n{\n  \"PredictedLabel\": \"spam\",\n  \"Confidence\": 0.94,\n  \"Scores\": [\n    { \"Label\": \"spam\", \"Score\": 0.94 },\n    { \"Label\": \"ham\",  \"Score\": 0.06 }\n  ],\n  \"Verdict\": \"Block\",\n  \"Blocked\": true,\n  \"Color\": \"#DC2626\",\n  \"DurationMs\": 1.72,\n  \"Explanation\": \"High confidence spam; message should be blocked.\"\n}\n```\n### Analytics\n- `GET /analytics` → lifetime process stats\n- `GET /analytics/last/{minutes}` → rolling window stats\n\n### Unsure insights\n- `GET /insights/unsure?take=50` → most recent unsure items\n- `GET /insights/unsure/last/{minutes}?take=50` → unsure items in a time window\n\n### Configuration diagnostics\n- `GET /config/diagnostics` → recommended settings and warnings\n\nExample curl:\n```bash\ncurl -X POST http://localhost:8080/classify \\\n  -H \"Content-Type: application/json\" \\\n  -H \"x-api-key: your-strong-key\" \\\n  -d '{\"Text\":\"win an instant discord nitro giveaway, click here!\"}'\n```\n\n## Model\n- The model file is `Models/SpamClassifier.mlnet`. It is copied to the output folder.\n- At runtime it is loaded from `AppContext.BaseDirectory/Models/SpamClassifier.mlnet`.\n- If you retrain, replace that file and rebuild/publish.\n\n## Architecture notes\n- A bounded `Channel\u003cClassificationRequest\u003e` is used to queue work.\n- A single background worker processes items to ensure `PredictionEngine` safety.\n- For higher throughput, consider adding more workers with separate prediction engine instances and measuring CPU/memory.\n\n## Security\n- Requests must include the `x-api-key` header matching `SAKI_ML_API_KEY`.\n- For production, terminate TLS at a reverse proxy or use container-level TLS.\n\n## Troubleshooting\n- 401 Unauthorized: ensure `x-api-key` header matches `SAKI_ML_API_KEY`.\n- Model not found: ensure `Models/SpamClassifier.mlnet` exists at build-time and is copied to the output.\n- Alpine runtime issues: the Alpine image includes `icu-libs`, `gcompat`, and other native deps required by ML.NET/TorchSharp.\n- GPU inference: not enabled in these images. For GPU, use appropriate CUDA base images and packages.\n\n## Development\nRun locally with the development key (for testing only):\n```bash\nSAKI_ML_API_KEY=dev-key dotnet run\n```\n\nThen call the API as shown above.\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fciderapp%2Fsaki-ml","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fciderapp%2Fsaki-ml","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fciderapp%2Fsaki-ml/lists"}