mirror of
https://github.com/mudler/LocalAI.git
synced 2026-08-04 12:22:22 -04:00
Compare commits
5 Commits
bot/issue-
...
bot/issue-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bc8113a8d6 | ||
|
|
e6b235baf2 | ||
|
|
0bedc75921 | ||
|
|
dad4d5956a | ||
|
|
42541dd4f6 |
@@ -359,6 +359,26 @@ GitHub Actions caches are limited to 10 GB per repo. Steady-state worst case: ~8
|
||||
|
||||
One residual self-hosted reference remains in `test-extra.yml` (`tests-vibevoice-cpp-grpc-transcription` uses `bigger-runner` for the 30s JFK-decode timeout headroom). That's a separate concern.
|
||||
|
||||
### Small always-on jobs routed to `arc-runner-set`
|
||||
|
||||
The hosted pool is shared across the whole *account*, not per repo, so a burst in one repo starves the others. On 2026-07-31 it went to **zero scheduled jobs for 35 consecutive minutes** with 39 jobs queued, while `arc-runner-set` completed 12 jobs without interruption over the same window. Actions was healthy globally at the time (other public repos were scheduling normally), so this is an account-level throttle, not an outage.
|
||||
|
||||
`gh-pages.yml` (`build` + `deploy`) is therefore routed to `arc-runner-set` when `github.repository == 'mudler/LocalAI'`. It needs no fork-safety clause because it only triggers on push-to-master and `workflow_dispatch`, so it never executes pull-request code. The repository guard keeps forks (which have no such runner label) from queueing forever. It fetches its own toolchains via `setup-go` / `actions-hugo` and uses no `sudo`/`apt`.
|
||||
|
||||
#### What the `arc-runner-set` image actually contains
|
||||
|
||||
Measured 2026-07-31 on run `30637392862` by a preflight step, not assumed:
|
||||
|
||||
| present | **absent** |
|
||||
|---|---|
|
||||
| `git`, `curl`, `unzip`, `tar`, `ldd`, `python3` | **`make`**, **`gcc`** |
|
||||
|
||||
That is why `lint.yml` is **not** on the self-hosted pool. Both of its jobs were routed there and both failed in one second: `golangci-lint` needs `make` (for `make protogen-go`, itself needing `curl`+`unzip` to fetch protoc, and for `make lint`), and `build-scripts` additionally needs a C toolchain because the packaging-script tests compile a throwaway binary and inspect it with `ldd`. Both jobs are back on `ubuntu-latest`.
|
||||
|
||||
The preflight steps were deliberately left in place. They cost about a second on the hosted pool and mean that whenever the runner image gains `make` + `gcc`, re-routing is one `runs-on:` line per job and any remaining gap reports itself by name rather than as an opaque mid-build failure.
|
||||
|
||||
Note for any future re-route: `lint.yml` also triggers on `pull_request`, and a fork PR runs untrusted contributor code. That must never reach a persistent self-hosted runner, so any re-route has to stay push-only, e.g. `${{ (github.event_name == 'push' && github.repository == 'mudler/LocalAI') && 'arc-runner-set' || 'ubuntu-latest' }}`.
|
||||
|
||||
## Touching the cache pipeline
|
||||
|
||||
When changing `image_build.yml`, `backend_build.yml`, any of the `backend/Dockerfile.*` files, `Dockerfile.base-grpc-builder`, `.docker/install-base-deps.sh`, `.docker/<backend>-compile.sh`, or `scripts/changed-backends.js`:
|
||||
|
||||
21
.github/workflows/gh-pages.yml
vendored
21
.github/workflows/gh-pages.yml
vendored
@@ -25,7 +25,20 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
# Self-hosted. This workflow is push-to-master + workflow_dispatch only, so
|
||||
# it never executes pull-request code and a fork cannot reach the runner
|
||||
# with untrusted changes. The repository guard keeps forks (whose own master
|
||||
# pushes would otherwise queue forever against a label they do not have) on
|
||||
# the hosted pool.
|
||||
#
|
||||
# Why: the GitHub-hosted pool is shared account-wide and has repeatedly
|
||||
# starved (2026-07-31: 35 consecutive minutes at zero scheduled jobs, while
|
||||
# arc-runner-set kept completing work throughout). Publishing the site is
|
||||
# small, frequent, and must not sit behind a saturated hosted queue.
|
||||
#
|
||||
# Needs only git, tar and curl on the runner: setup-go and actions-hugo
|
||||
# fetch their own toolchains, and no step uses sudo, apt, make or unzip.
|
||||
runs-on: ${{ github.repository == 'mudler/LocalAI' && 'arc-runner-set' || 'ubuntu-latest' }}
|
||||
env:
|
||||
HUGO_VERSION: "0.146.3"
|
||||
steps:
|
||||
@@ -86,7 +99,11 @@ jobs:
|
||||
environment:
|
||||
name: github-pages
|
||||
url: ${{ steps.deployment.outputs.page_url }}
|
||||
runs-on: ubuntu-latest
|
||||
# Same routing as build: a hosted slot for a ~10s deploy is exactly the kind
|
||||
# of job that should not block on a starved pool. deploy-pages authenticates
|
||||
# with the job's OIDC token (id-token: write above), which self-hosted
|
||||
# runners issue the same way hosted ones do.
|
||||
runs-on: ${{ github.repository == 'mudler/LocalAI' && 'arc-runner-set' || 'ubuntu-latest' }}
|
||||
needs: build
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
|
||||
55
.github/workflows/lint.yml
vendored
55
.github/workflows/lint.yml
vendored
@@ -21,8 +21,41 @@ concurrency:
|
||||
|
||||
jobs:
|
||||
golangci-lint:
|
||||
# Self-hosted for PUSH only, and only in the canonical repo.
|
||||
#
|
||||
# This workflow also runs on pull_request, which for a fork PR means
|
||||
# executing untrusted contributor code. That must never land on a
|
||||
# self-hosted runner, so anything that is not a push to mudler/LocalAI stays
|
||||
# on the ephemeral hosted pool. Pushes to master are trusted code that has
|
||||
# already been reviewed and merged.
|
||||
#
|
||||
# Why at all: the hosted pool is shared account-wide and starved for 35
|
||||
# straight minutes on 2026-07-31 while arc-runner-set kept completing jobs.
|
||||
# Lint is small and runs on every commit, so it is a good candidate to move
|
||||
# off the contended pool.
|
||||
# REVERTED to hosted: the arc-runner-set image has git, curl, unzip, tar,
|
||||
# ldd and python3, but NOT make (nor gcc). Measured on run 30637392862,
|
||||
# where the preflight below named both. Re-route here once the runner image
|
||||
# ships a C toolchain and make; the preflight stays so the next attempt
|
||||
# fails by name in one second instead of opaquely mid-build.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Preflight - required host tools
|
||||
# The hosted images ship these; a self-hosted container image may not.
|
||||
# Check up front so a missing tool reports itself by name instead of
|
||||
# surfacing as an opaque failure inside `make protogen-go` (which needs
|
||||
# curl + unzip for protoc) or `make lint`.
|
||||
run: |
|
||||
missing=""
|
||||
for t in git curl unzip make tar; do
|
||||
command -v "$t" >/dev/null 2>&1 || missing="$missing $t"
|
||||
done
|
||||
echo "runner: ${RUNNER_NAME:-unknown} os: $(uname -sm)"
|
||||
if [ -n "$missing" ]; then
|
||||
echo "::error::missing required tools on this runner:$missing"
|
||||
exit 1
|
||||
fi
|
||||
echo "all required tools present"
|
||||
- uses: actions/checkout@v7
|
||||
with:
|
||||
# Full history so golangci-lint's new-from-merge-base can reach
|
||||
@@ -55,8 +88,30 @@ jobs:
|
||||
# container build (a missing transitive dep, a partial cuDNN family). Their
|
||||
# shell tests need nothing but bash + gcc + ldd, so run them on every PR
|
||||
# rather than waiting on a multi-GB cross-arch backend image build.
|
||||
#
|
||||
# Push-only self-hosted routing, same fork-safety reasoning as
|
||||
# golangci-lint above.
|
||||
# REVERTED to hosted: the arc-runner-set image has git, curl, unzip, tar,
|
||||
# ldd and python3, but NOT make (nor gcc). Measured on run 30637392862,
|
||||
# where the preflight below named both. Re-route here once the runner image
|
||||
# ships a C toolchain and make; the preflight stays so the next attempt
|
||||
# fails by name in one second instead of opaquely mid-build.
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Preflight - required host tools
|
||||
# This job additionally needs a C toolchain: the packaging-script tests
|
||||
# compile a throwaway binary and inspect it with ldd.
|
||||
run: |
|
||||
missing=""
|
||||
for t in git make gcc ldd python3; do
|
||||
command -v "$t" >/dev/null 2>&1 || missing="$missing $t"
|
||||
done
|
||||
echo "runner: ${RUNNER_NAME:-unknown} os: $(uname -sm)"
|
||||
if [ -n "$missing" ]; then
|
||||
echo "::error::missing required tools on this runner:$missing"
|
||||
exit 1
|
||||
fi
|
||||
echo "all required tools present"
|
||||
- uses: actions/checkout@v7
|
||||
- name: run packaging script tests
|
||||
run: make test-build-scripts
|
||||
|
||||
@@ -236,8 +236,8 @@ var _ = Describe("InstallModelFromGallery with an empty base config", func() {
|
||||
Expect(install(e.Name, gallery.GalleryModel{})).To(Succeed())
|
||||
cfg := installedConfig(e.Name)
|
||||
Expect(cfg["name"]).To(Equal(e.Name))
|
||||
// The catalog's own overrides, verbatim, laid over the empty base.
|
||||
Expect(cfg["parameters"]).To(Equal(e.Overrides["parameters"]))
|
||||
// The catalog's model override survives inference-default enrichment.
|
||||
Expect(cfg["parameters"]).To(HaveKeyWithValue("model", "LiquidAI_LFM2-1.2B-RAG-Q4_K_M.gguf"))
|
||||
Expect(cfg["known_usecases"]).To(Equal(e.Overrides["known_usecases"]))
|
||||
})
|
||||
})
|
||||
|
||||
90
core/gallery/inference_defaults_install_test.go
Normal file
90
core/gallery/inference_defaults_install_test.go
Normal file
@@ -0,0 +1,90 @@
|
||||
package gallery_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
. "github.com/onsi/ginkgo/v2"
|
||||
. "github.com/onsi/gomega"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/mudler/LocalAI/core/gallery"
|
||||
"github.com/mudler/LocalAI/pkg/modelartifacts"
|
||||
"github.com/mudler/LocalAI/pkg/system"
|
||||
)
|
||||
|
||||
var _ = Describe("gallery inference defaults", func() {
|
||||
readPersistedConfig := func(modelsPath, name string) map[string]any {
|
||||
data, err := os.ReadFile(filepath.Join(modelsPath, name+".yaml"))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
persisted := map[string]any{}
|
||||
Expect(yaml.Unmarshal(data, &persisted)).To(Succeed())
|
||||
return persisted
|
||||
}
|
||||
|
||||
expectNestedDefaults := func(persisted map[string]any) {
|
||||
Expect(persisted).NotTo(HaveKey("temperature"))
|
||||
Expect(persisted).NotTo(HaveKey("top_p"))
|
||||
parameters, ok := persisted["parameters"].(map[string]any)
|
||||
Expect(ok).To(BeTrue())
|
||||
Expect(parameters).To(HaveKeyWithValue("temperature", 0.7))
|
||||
Expect(parameters).To(HaveKeyWithValue("top_p", 0.42))
|
||||
Expect(parameters).To(HaveKeyWithValue("top_k", 20))
|
||||
Expect(parameters).To(HaveKeyWithValue("min_p", 0))
|
||||
Expect(parameters).To(HaveKeyWithValue("repeat_penalty", 1))
|
||||
Expect(parameters).To(HaveKeyWithValue("presence_penalty", 1.5))
|
||||
}
|
||||
|
||||
It("persists defaults under parameters after artifact binding", func() {
|
||||
modelsPath := GinkgoT().TempDir()
|
||||
state, err := system.GetSystemState(system.WithModelPath(modelsPath))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
resolved := modelartifacts.Spec{
|
||||
Name: "model", Target: "model",
|
||||
Source: modelartifacts.Source{Type: "huggingface", Repo: "owner/qwen3.5-model", Revision: "main"},
|
||||
Resolved: &modelartifacts.Resolved{
|
||||
Endpoint: "https://huggingface.co",
|
||||
Revision: "0123456789abcdef0123456789abcdef01234567",
|
||||
CacheKey: "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
},
|
||||
}
|
||||
fake := &fakeArtifactMaterializer{result: modelartifacts.Result{Spec: resolved}}
|
||||
definition := &gallery.ModelConfig{Name: "qwen3.5-artifact", ConfigFile: `
|
||||
backend: transformers
|
||||
artifacts:
|
||||
- name: model
|
||||
target: model
|
||||
source: {type: huggingface, repo: owner/qwen3.5-model}
|
||||
parameters:
|
||||
model: owner/qwen3.5-model
|
||||
top_p: 0.42
|
||||
`}
|
||||
|
||||
_, err = gallery.InstallModel(context.Background(), state, "", definition, nil, nil, false,
|
||||
gallery.WithArtifactMaterializer(fake))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectNestedDefaults(readPersistedConfig(modelsPath, definition.Name))
|
||||
})
|
||||
|
||||
It("persists defaults under parameters when the entry declares files", func() {
|
||||
modelsPath := GinkgoT().TempDir()
|
||||
state, err := system.GetSystemState(system.WithModelPath(modelsPath))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(os.WriteFile(filepath.Join(modelsPath, "weights.gguf"), []byte("weights"), 0644)).To(Succeed())
|
||||
definition := &gallery.ModelConfig{
|
||||
Name: "qwen3.5-files",
|
||||
ConfigFile: `
|
||||
backend: llama-cpp
|
||||
parameters:
|
||||
model: weights.gguf
|
||||
top_p: 0.42
|
||||
`,
|
||||
Files: []gallery.File{{Filename: "weights.gguf", URI: "https://example.com/weights.gguf"}},
|
||||
}
|
||||
|
||||
_, err = gallery.InstallModel(context.Background(), state, "", definition, nil, nil, false)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectNestedDefaults(readPersistedConfig(modelsPath, definition.Name))
|
||||
})
|
||||
})
|
||||
@@ -622,35 +622,41 @@ func InstallModel(ctx context.Context, systemState *system.SystemState, nameOver
|
||||
lconfig.ApplyInferenceDefaults(&modelConfig, name, modelConfig.Model)
|
||||
|
||||
// Merge inference defaults into configMap so they are persisted without losing unknown fields.
|
||||
defaults := make(map[string]any)
|
||||
if modelConfig.Temperature != nil {
|
||||
if _, exists := configMap["temperature"]; !exists {
|
||||
configMap["temperature"] = *modelConfig.Temperature
|
||||
}
|
||||
defaults["temperature"] = *modelConfig.Temperature
|
||||
}
|
||||
if modelConfig.TopP != nil {
|
||||
if _, exists := configMap["top_p"]; !exists {
|
||||
configMap["top_p"] = *modelConfig.TopP
|
||||
}
|
||||
defaults["top_p"] = *modelConfig.TopP
|
||||
}
|
||||
if modelConfig.TopK != nil {
|
||||
if _, exists := configMap["top_k"]; !exists {
|
||||
configMap["top_k"] = *modelConfig.TopK
|
||||
}
|
||||
defaults["top_k"] = *modelConfig.TopK
|
||||
}
|
||||
if modelConfig.MinP != nil {
|
||||
if _, exists := configMap["min_p"]; !exists {
|
||||
configMap["min_p"] = *modelConfig.MinP
|
||||
}
|
||||
defaults["min_p"] = *modelConfig.MinP
|
||||
}
|
||||
if modelConfig.RepeatPenalty != 0 {
|
||||
if _, exists := configMap["repeat_penalty"]; !exists {
|
||||
configMap["repeat_penalty"] = modelConfig.RepeatPenalty
|
||||
}
|
||||
defaults["repeat_penalty"] = modelConfig.RepeatPenalty
|
||||
}
|
||||
if modelConfig.PresencePenalty != 0 {
|
||||
if _, exists := configMap["presence_penalty"]; !exists {
|
||||
configMap["presence_penalty"] = modelConfig.PresencePenalty
|
||||
defaults["presence_penalty"] = modelConfig.PresencePenalty
|
||||
}
|
||||
if len(defaults) > 0 {
|
||||
parameters, ok := configMap["parameters"].(map[string]any)
|
||||
if !ok {
|
||||
parameters = make(map[string]any)
|
||||
configMap["parameters"] = parameters
|
||||
}
|
||||
for key, value := range defaults {
|
||||
if _, exists := parameters[key]; !exists {
|
||||
parameters[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
updatedConfigYAML, err = yaml.Marshal(configMap)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal config with inference defaults: %v", err)
|
||||
}
|
||||
|
||||
if valid, err := modelConfig.Validate(); !valid {
|
||||
|
||||
@@ -19,6 +19,10 @@
|
||||
|
||||
contributors:
|
||||
note: "Engineers whose public profile names these employers have commits in the repository."
|
||||
# The landing page prints the names only, not the per-company counts: a "1
|
||||
# commit" stamp next to a large employer reads as weakness even though the
|
||||
# claim is exactly as true. The counts stay here because they are the
|
||||
# provenance for the list, and anyone re-checking it needs them.
|
||||
companies:
|
||||
- { name: "Microsoft", people: 3, commits: 20 }
|
||||
- { name: "Spectro Cloud", people: 3, commits: 11 }
|
||||
@@ -51,47 +55,141 @@ contributors:
|
||||
|
||||
integrations:
|
||||
note: "These projects reference LocalAI in their own repository or documentation."
|
||||
# Admission rule: the project's OWN repository or official docs must name
|
||||
# LocalAI as a provider, backend or integration. A blog post saying so is not
|
||||
# enough, and neither is a generic "any OpenAI-compatible server" line that
|
||||
# never names us. Every entry below was opened and read before being added,
|
||||
# and the quote that justifies it is in the pull request that added it.
|
||||
#
|
||||
# Deliberately excluded after checking: LiteLLM, n8n, Home Assistant core,
|
||||
# BionicGPT, Semantic Kernel, Spring AI, Jan, CrewAI, Haystack, Onyx,
|
||||
# SillyTavern, Portkey and Kong all have zero mentions in their own repos.
|
||||
# PrivateGPT and Langflow name us only in passing comparisons or a tooltip.
|
||||
# Tools4AI has a LocalAI processor whose methods return null.
|
||||
projects:
|
||||
- name: AnythingLLM
|
||||
by: Mintplex Labs
|
||||
url: https://github.com/Mintplex-Labs/anything-llm
|
||||
what: "Runs LocalAI as a model provider for its chat and document workspaces."
|
||||
- name: langchain4j
|
||||
url: https://github.com/langchain4j/langchain4j
|
||||
what: "Ships a LocalAI module, so JVM applications can target it directly."
|
||||
- name: LangChain
|
||||
url: https://python.langchain.com/docs/integrations/providers/localai/
|
||||
what: "Documents LocalAI as a provider in the Python integrations."
|
||||
- name: LlamaIndex
|
||||
url: https://docs.llamaindex.ai/en/stable/examples/llm/localai/
|
||||
what: "Documents LocalAI as an OpenAI-compatible LLM you can point it at."
|
||||
- name: Open WebUI
|
||||
url: https://docs.openwebui.com/getting-started/quick-start/connect-a-provider/starting-with-openai-compatible
|
||||
what: "Connects to LocalAI as an OpenAI-compatible local server."
|
||||
- name: Dify
|
||||
by: LangGenius
|
||||
url: https://marketplace.dify.ai/plugin/langgenius/localai
|
||||
what: "Ships an official LocalAI plugin for inference and embeddings."
|
||||
- name: LibreChat
|
||||
url: https://www.librechat.ai/docs/configuration/librechat_yaml/object_structure/speech
|
||||
what: "Uses LocalAI as a speech provider for text to speech."
|
||||
- name: RAGFlow
|
||||
by: InfiniFlow
|
||||
url: https://ragflow.io/docs/supported_models
|
||||
what: "Lists LocalAI as a model provider, and ships a driver for it including rerank."
|
||||
- name: Flowise
|
||||
by: FlowiseAI
|
||||
url: https://github.com/FlowiseAI/Flowise
|
||||
what: "Offers LocalAI as a node for chat and embeddings in its visual builder."
|
||||
- name: Continue
|
||||
url: https://docs.continue.dev/customize/model-providers/top-level/openai
|
||||
what: "Lists LocalAI among the OpenAI-compatible providers the editor can target."
|
||||
- name: big-AGI
|
||||
url: https://github.com/enricoros/big-AGI/blob/main/docs/config-local-localai.md
|
||||
what: "Adds LocalAI as a model source, with its own setup page and env vars."
|
||||
- name: K8sGPT
|
||||
by: CNCF Sandbox
|
||||
url: https://docs.k8sgpt.ai/reference/providers/backend/
|
||||
what: "Lists LocalAI as an analysis backend, alongside Bedrock and Azure OpenAI."
|
||||
- name: k8sgpt-operator
|
||||
by: k8sgpt-ai
|
||||
url: https://github.com/k8sgpt-ai/k8sgpt-operator
|
||||
what: "Sets LocalAI as the backend on the K8sGPT custom resource, with no OpenAI secret."
|
||||
- name: Nextcloud
|
||||
url: https://apps.nextcloud.com/apps/integration_openai
|
||||
what: "Points its text, image and speech features at a self-hosted LocalAI."
|
||||
- name: Frigate
|
||||
url: https://github.com/blakeblackshear/frigate/blob/dev/docs/docs/configuration/genai/config.md
|
||||
what: "Names LocalAI as a server for its generative AI features."
|
||||
# Present in the dev branch docs source; not yet on the published site.
|
||||
- name: Kairos
|
||||
url: https://github.com/kairos-io/kairos
|
||||
what: "Ships LocalAI as part of its immutable Linux distribution."
|
||||
- name: AIKit
|
||||
by: Sertac Ozercan
|
||||
url: https://github.com/sozercan/aikit
|
||||
what: "Builds fine-tuned models into images that serve through LocalAI."
|
||||
- name: Kairos
|
||||
url: https://github.com/kairos-io/kairos
|
||||
what: "Ships LocalAI as part of its immutable Linux distribution."
|
||||
- name: langchain4j
|
||||
url: https://github.com/langchain4j/langchain4j
|
||||
what: "Ships a LocalAI module, so JVM applications can target it directly."
|
||||
- name: promptfoo
|
||||
url: https://www.promptfoo.dev/docs/providers/localai/
|
||||
what: "Provides a localai: prefix so evals run against chat, completion and embeddings."
|
||||
- name: Mods
|
||||
by: Charm
|
||||
url: https://github.com/charmbracelet/mods
|
||||
what: "Reads a LocalAI endpoint from its config, so the CLI pipes shell output to a local model."
|
||||
- name: TypingMind
|
||||
url: https://docs.typingmind.com/manage-and-connect-ai-models/local-ai
|
||||
what: "Documents pointing a custom model endpoint at a LocalAI server."
|
||||
- name: baibot
|
||||
by: etke.cc
|
||||
url: https://github.com/etkecc/baibot/blob/main/docs/providers.md
|
||||
what: "Has a first-class localai provider for text, speech to text and text to speech."
|
||||
- name: LLM Vision
|
||||
by: Home Assistant
|
||||
url: https://github.com/valentinfrlch/ha-llmvision
|
||||
what: "Lists LocalAI as a provider for analysing camera images and video feeds."
|
||||
- name: VoxInput
|
||||
url: https://github.com/richiejp/VoxInput
|
||||
what: "Sends desktop voice input to LocalAI for transcription and a realtime assistant."
|
||||
- name: Obsidian BMO Chatbot
|
||||
url: https://github.com/longy2k/obsidian-bmo-chatbot
|
||||
what: "Lists LocalAI as a self-hosted endpoint for the note-taking chat plugin."
|
||||
- name: ShellOracle
|
||||
url: https://github.com/djcopley/ShellOracle
|
||||
what: "Ships a LocalAI provider for turning natural language into shell commands."
|
||||
- name: QA-Pilot
|
||||
url: https://github.com/reid41/QA-Pilot
|
||||
what: "Runs its repository chat against a LocalAI base URL."
|
||||
|
||||
press:
|
||||
note: "Written about LocalAI, by people who do not work on it."
|
||||
# One card per outlet. SUSE published four posts, and listing them as four
|
||||
# entries made a single vendor look like the whole of the coverage; as one
|
||||
# series entry the repetition becomes the point instead of the problem.
|
||||
#
|
||||
# Every entry here has been opened and checked to confirm it is about this
|
||||
# project. Two earlier "coverage" links were not: a modelslab.com piece that
|
||||
# reviewed Frikallo/parakeet.cpp, an unrelated project of the same name, and
|
||||
# a snailtext.app benchmark of Parakeet through ONNX Runtime that never
|
||||
# mentioned LocalAI at all. Verify before adding.
|
||||
articles:
|
||||
- outlet: Pulumi
|
||||
title: "Deploy low-code LLM apps on AWS with Flowise and LocalAI"
|
||||
author: "Engin Diri"
|
||||
date: "2024-02-26"
|
||||
url: https://www.pulumi.com/blog/low-code-llm-apps-with-local-ai-flowise-and-pulumi/
|
||||
what: "A full EKS deployment, with LocalAI doing inference and no GPU in the cluster."
|
||||
- outlet: Semaphore
|
||||
title: "LocalAI: replacing OpenAI API with open-source"
|
||||
author: "Tomas Fernandez"
|
||||
date: "2023-12-19"
|
||||
url: https://semaphore.io/blog/localai
|
||||
what: "Swapping an existing OpenAI integration over to LocalAI, endpoint by endpoint."
|
||||
- outlet: Spectro Cloud
|
||||
title: "K8sGPT + LocalAI: unlock Kubernetes superpowers for free"
|
||||
author: "Tyler Gillson"
|
||||
date: "2023-04-29"
|
||||
url: https://www.spectrocloud.com/blog/k8sgpt-localai-unlock-kubernetes-superpowers-for-free
|
||||
what: "Cluster diagnosis through K8sGPT, answered by a model running on CPU."
|
||||
- outlet: SUSE
|
||||
title: "Running AI locally"
|
||||
title: "A four-part series on running AI locally"
|
||||
author: "Christian Huller"
|
||||
date: "2025-11-24"
|
||||
url: https://www.suse.com/c/running-ai-locally/
|
||||
what: "Deploying LocalAI on openSUSE, then driving it from VS Code."
|
||||
- outlet: SUSE
|
||||
title: "Adding own documents to your local AI using RAG"
|
||||
url: https://www.suse.com/c/adding-own-documents-to-your-local-ai-using-rag/
|
||||
what: "Retrieval over your own documents, on top of LocalAI."
|
||||
- outlet: SUSE
|
||||
title: "Local AI and Confluence"
|
||||
url: https://www.suse.com/c/local-ai-and-confluence/
|
||||
what: "Putting a Confluence space behind a local model."
|
||||
- outlet: SUSE
|
||||
title: "Introduction to AI training with openSUSE"
|
||||
url: https://www.suse.com/c/introduction-to-ai-training-with-opensuse/
|
||||
what: "Training a LoRA and serving it through LocalAI."
|
||||
what: "Install on openSUSE and drive it from VS Code, add your own documents with RAG, put a Confluence space behind a local model, then train a LoRA and serve it."
|
||||
|
||||
@@ -18,6 +18,7 @@ enableEmoji = true
|
||||
huggingface = 'https://huggingface.co/mudler'
|
||||
# Refreshed by hand or by CI; shown in the nav and the traction band.
|
||||
stars = '48,042'
|
||||
contributors = '224'
|
||||
|
||||
[markup.goldmark.renderer]
|
||||
unsafe = true
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<div class="acts fd">
|
||||
<a class="btn" href="#start">Install LocalAI <span>→</span></a>
|
||||
<a class="btn btn--o" href="/docs/">Read the docs</a>
|
||||
<a class="btn btn--o" href="{{ .Site.Params.github }}">★ Star on GitHub</a>
|
||||
</div>
|
||||
<div class="figures fd">
|
||||
<div><b class="tnum" data-count="48042">0</b><span>GitHub stars</span></div>
|
||||
@@ -279,7 +280,7 @@
|
||||
</div>
|
||||
<p class="mono rv" style="margin-top:.9rem;font-size:.66rem;color:var(--p-dim)">Qwen3.5-35B-A3B on an NVIDIA DGX Spark (GB10). Perplexity on wikitext-2-raw at context 2048. Full methodology in the technical report.</p>
|
||||
<div class="acts rv">
|
||||
<a class="btn" href="https://huggingface.co/collections/mudler/apex-quants">APEX models on Hugging Face ↗</a>
|
||||
<a class="btn" href="https://huggingface.co/collections/mudler/apex-quants-gguf">APEX models on Hugging Face ↗</a>
|
||||
<a class="btn btn--o" href="https://github.com/localai-org/apex-quant">Technical report ↗</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -397,35 +398,9 @@
|
||||
<span>Deutsch</span><span>Español</span><span>français</span><span>日本語</span>
|
||||
<span>한국어</span><span>Português</span><span>Русский</span><span>中文</span>
|
||||
</div>
|
||||
<p class="kicker rv mt3">Who turns up around it</p>
|
||||
<h3 class="eco__h rv">Engineers from these companies have put code in</h3>
|
||||
<p class="eco__note rv">Every name below is a person with commits in the repository whose public profile
|
||||
names that employer. It is a claim about them, not about their employer, and the commit history is
|
||||
there if you want to check it. Organisations that want to say they use LocalAI add themselves to
|
||||
<a href="https://github.com/mudler/LocalAI/blob/master/ADOPTERS.md">ADOPTERS.md</a>.</p>
|
||||
<ul class="eco eco--co rv">
|
||||
{{- range .Site.Data.ecosystem.contributors.companies }}
|
||||
<li><b>{{ .name }}</b><span>{{ .commits }} commit{{ if ne .commits 1 }}s{{ end }}{{ if gt .people 1 }}, {{ .people }} people{{ end }}</span></li>
|
||||
{{- end }}
|
||||
</ul>
|
||||
<p class="eco__aside rv">Plus researchers at {{ delimit .Site.Data.ecosystem.contributors.academia ", " " and " }}.</p>
|
||||
|
||||
<h3 class="eco__h rv mt3">And these projects integrate it</h3>
|
||||
<p class="eco__note rv">Each one references LocalAI in its own repository or documentation, so you can
|
||||
verify any of them without taking our word for it.</p>
|
||||
<div class="eco eco--int rv">
|
||||
{{- range .Site.Data.ecosystem.integrations.projects }}
|
||||
<a href="{{ .url }}"><b>{{ .name }}</b>{{ with .by }}<em>{{ . }}</em>{{ end }}<span>{{ .what }}</span></a>
|
||||
{{- end }}
|
||||
</div>
|
||||
|
||||
<h3 class="eco__h rv mt3">Written about elsewhere</h3>
|
||||
<div class="eco eco--press rv">
|
||||
{{- range .Site.Data.ecosystem.press.articles }}
|
||||
<a href="{{ .url }}"><b>{{ .outlet }}</b><span>{{ .title }}</span><em>{{ .what }}</em></a>
|
||||
{{- end }}
|
||||
</div>
|
||||
|
||||
{{/* The strongest sentence in this section is somebody else's, so it opens
|
||||
the section rather than closing it. Everything below is supporting
|
||||
evidence for what these three people already said. */}}
|
||||
<p class="kicker rv mt3">What other people say</p>
|
||||
<div class="headline">
|
||||
<a class="hq rv" href="https://x.com/ggerganov/status/2065447087311917459">
|
||||
@@ -442,6 +417,45 @@
|
||||
<span class="hq__go">Read the post ↗</span></a>
|
||||
</div>
|
||||
|
||||
{{/* Names run as a sentence rather than a grid of pills. A pill wall of
|
||||
employers reads as a customer logo wall, which is a claim we are not
|
||||
making; a sentence keeps it about the people, which is the true one. */}}
|
||||
<p class="kicker rv mt3">Who shows up</p>
|
||||
<h3 class="eco__h rv">{{ .Site.Params.contributors }} people have put code in this repository.</h3>
|
||||
{{- $co := slice }}
|
||||
{{- range .Site.Data.ecosystem.contributors.companies }}{{ $co = $co | append (printf "<b>%s</b>" .name) }}{{ end }}
|
||||
{{- $ac := slice }}
|
||||
{{- range .Site.Data.ecosystem.contributors.academia }}{{ $ac = $ac | append (printf "<b>%s</b>" .) }}{{ end }}
|
||||
<p class="eco__names rv">Some of them do it from a desk at {{ delimit $co ", " " and " | safeHTML }}.
|
||||
Others from labs at {{ delimit $ac ", " " and " | safeHTML }}.</p>
|
||||
<p class="eco__note rv">That is where they work, not who sent them.
|
||||
<a href="{{ .Site.Params.github }}/graphs/contributors">Check out the full contributor list, 200+ and growing ↗</a></p>
|
||||
|
||||
{{/* The list runs as a marquee because the count is the argument: any one
|
||||
of these is a weak signal, and the whole moving line is the strong one.
|
||||
It pauses on hover so the links stay usable. */}}
|
||||
<h3 class="eco__h rv mt3">And many projects integrate it</h3>
|
||||
<p class="eco__note rv">Click any name to see where they say so.</p>
|
||||
<div class="reel reel--int rv">
|
||||
<div class="reel__t">
|
||||
{{- range .Site.Data.ecosystem.integrations.projects }}
|
||||
<a href="{{ .url }}">{{ .name }}</a>
|
||||
{{- end }}
|
||||
{{- range .Site.Data.ecosystem.integrations.projects }}
|
||||
<a href="{{ .url }}" tabindex="-1" aria-hidden="true">{{ .name }}</a>
|
||||
{{- end }}
|
||||
</div>
|
||||
</div>
|
||||
<p class="eco__aside rv">Using LocalAI at work? Add your organisation to
|
||||
<a href="{{ .Site.Params.github }}/blob/master/ADOPTERS.md">ADOPTERS.md</a>.</p>
|
||||
|
||||
<h3 class="eco__h rv mt3">Written about elsewhere</h3>
|
||||
<div class="eco eco--press rv">
|
||||
{{- range .Site.Data.ecosystem.press.articles }}
|
||||
<a href="{{ .url }}"><b>{{ .outlet }}</b><span>{{ .title }}</span><em>{{ .what }}</em></a>
|
||||
{{- end }}
|
||||
</div>
|
||||
|
||||
<p class="kicker rv mt3">Built on, integrated with, written about</p>
|
||||
<div class="posts">
|
||||
<a class="post rv" href="https://x.com/sozercan/status/1769769695081546236">
|
||||
@@ -477,17 +491,6 @@
|
||||
<p>Oh, high there @LocalAI_API, nice to see a terminal based UI for ya! (it's a WIP, but just wanted something cleaner then CURL calls)</p>
|
||||
<span class="post__go">On X, 2023 ↗</span></a>
|
||||
</div>
|
||||
<div class="cards">
|
||||
<a class="cd rv" href="https://modelslab.com/blog/audio-generation/parakeet-cpp-vs-whisper-self-hosted-asr-comparison-2026"><p class="cd__k">Coverage</p>
|
||||
<h3>parakeet.cpp vs Whisper</h3><p>An independent comparison of self-hosted ASR options put parakeet.cpp against Whisper on accuracy and speed.</p>
|
||||
<span class="cite">modelslab.com ↗</span></a>
|
||||
<a class="cd rv" href="https://snailtext.app/blog/whisper-vs-parakeet-tdt/"><p class="cd__k">Coverage</p>
|
||||
<h3>Roughly 4x faster on CPU</h3><p>A third-party benchmark measured Parakeet TDT against Whisper Small through whisper.cpp on the same machine.</p>
|
||||
<span class="cite">snailtext.app ↗</span></a>
|
||||
<a class="cd rv" href="https://github.com/mudler/LocalAI/graphs/contributors"><p class="cd__k">Contributors</p>
|
||||
<h3>224 people have shipped code</h3><p>Backends, gallery entries, docs, translations and bug fixes, from people who are not on the team.</p>
|
||||
<span class="cite">github.com ↗</span></a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -496,15 +499,17 @@
|
||||
<div class="shell">
|
||||
<div class="bars rv" aria-hidden="true"><i></i><i></i><i></i><i></i></div>
|
||||
<p class="kicker rv">From the team</p>
|
||||
<h2 class="rv mt1" style="max-width:20ch">Every release gets written up and demonstrated.</h2>
|
||||
<p class="lede rv mt2">A changelog tells you what moved. These posts show you what it does, with a recording you can watch and a command you can run.</p>
|
||||
<h2 class="rv mt1" style="max-width:22ch">We publish the numbers, including the ones that cost us.</h2>
|
||||
<p class="lede rv mt2">APEX takes a 35B model from 64.6 GB to 12.2 GB, and from 30.4 to 74.4 tokens a second. Perplexity goes from 6.537 to 7.088. That trade is in the post too, with the command that produced it.</p>
|
||||
{{/* Pulled from the posts themselves. The cards used to be hand-written,
|
||||
which is how one of them ended up advertising a post that did not
|
||||
exist, and how all three linked to the index instead of the article. */}}
|
||||
<div class="cards">
|
||||
<a class="cd rv" href="/blog/"><p class="cd__k">Release · v4.8.0</p><h3>What landed in LocalAI 4.8</h3>
|
||||
<p>The audio.cpp backend, Valkey vector search, configurable VAE tiling, and a faster realtime path.</p><span class="cd__go">Read the post →</span></a>
|
||||
<a class="cd rv" href="/blog/"><p class="cd__k">Engineering</p><h3>Porting vLLM to C++</h3>
|
||||
<p>What paged attention looks like without a Python runtime, and what it cost us to get there.</p><span class="cd__go">Read the post →</span></a>
|
||||
<a class="cd rv" href="/blog/"><p class="cd__k">Benchmarks</p><h3>How APEX beats Q8_0 at a third of the size</h3>
|
||||
<p>Per-tensor, per-layer precision for mixture-of-experts models, measured against the alternatives.</p><span class="cd__go">Read the post →</span></a>
|
||||
{{- range first 3 (where .Site.RegularPages "Section" "blog") }}
|
||||
<a class="cd rv" href="{{ .RelPermalink }}">
|
||||
<p class="cd__k">{{ .Date.Format "2 January 2006" }}</p><h3>{{ .Title }}</h3>
|
||||
<p>{{ .Params.summary | truncate 155 }}</p><span class="cd__go">Read the post →</span></a>
|
||||
{{- end }}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
<div><h4>Community</h4><ul><li><a href="https://github.com/mudler/LocalAI">GitHub</a></li><li><a href="https://discord.gg/uJAeKSAGDy">Discord</a></li><li><a href="https://twitter.com/LocalAI_API">X</a></li><li><a href="https://huggingface.co/mudler">Hugging Face</a></li></ul></div>
|
||||
<div><h4>Support us</h4><ul><li><a href="https://github.com/sponsors/mudler">Sponsor</a></li><li><a href="https://github.com/mudler/LocalAI/graphs/contributors">Contributors</a></li><li><a href="/docs/">Contributing</a></li></ul></div>
|
||||
</div>
|
||||
<div class="foot__n"><span>© 2026 LocalAI</span><span>MIT licence</span><span>Design mock, not a live page</span></div>
|
||||
<div class="foot__n"><span>© 2026 LocalAI</span><span>MIT licence</span><span><a href="{{ .Site.Params.github }}">Source on GitHub</a></span></div>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
<a href="{{ .Site.Params.docsURL }}">Docs</a>
|
||||
</nav>
|
||||
<div class="topright">
|
||||
<a class="pill" href="{{ .Site.Params.github }}/stargazers">★ {{ .Site.Params.stars }}</a>
|
||||
<a class="pill" href="{{ .Site.Params.github }}">★ {{ .Site.Params.stars }}</a>
|
||||
<a class="go-btn" href="{{ $home }}#start">Install</a>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
@@ -288,6 +288,15 @@ h2{font-size:clamp(2.1rem,5vw,3.9rem)}
|
||||
.reel span::before{content:"/ ";color:var(--cyan)}
|
||||
@keyframes roll{to{transform:translateX(-50%)}}
|
||||
|
||||
/* Integrations reel. Same motion as the engines reel, but the items are links,
|
||||
so it stops on hover or keyboard focus and the names stay clickable. */
|
||||
.reel--int .reel__t{animation-duration:64s}
|
||||
.reel--int:hover .reel__t,.reel--int:focus-within .reel__t{animation-play-state:paused}
|
||||
.reel--int a{font-family:'Geist Mono',monospace;font-size:.78rem;color:var(--dim);white-space:nowrap;
|
||||
transition:color .18s}
|
||||
.reel--int a::before{content:"/ ";color:var(--cyan)}
|
||||
.reel--int a:hover,.reel--int a:focus-visible{color:var(--ink)}
|
||||
|
||||
/* apex */
|
||||
.tw{overflow-x:auto;margin-top:2.2rem;border:1px solid var(--p-line);border-radius:8px;background:#fff}
|
||||
table{border-collapse:collapse;width:100%;min-width:640px;font-variant-numeric:tabular-nums}
|
||||
@@ -344,10 +353,16 @@ tbody tr:hover td{background:rgba(95,205,228,.10)}
|
||||
letter-spacing:-.02em;color:var(--cyan);font-variant-numeric:tabular-nums}
|
||||
.big span{font-family:'Geist Mono',monospace;font-size:.58rem;letter-spacing:.11em;
|
||||
text-transform:uppercase;color:var(--faint)}
|
||||
.tl{margin-top:2.6rem;border-top:1px solid var(--line2);overflow-x:auto}
|
||||
.tl__t{display:flex;gap:0;min-width:min-content;padding-top:1.4rem}
|
||||
.tl__i{flex:0 0 15rem;padding:0 1.3rem 0 0;position:relative}
|
||||
.tl__i::before{content:"";position:absolute;left:0;top:-1.4rem;width:7px;height:7px;border-radius:50%;
|
||||
/* Six 15rem columns need 90rem, so the old flex row scrolled sideways on any
|
||||
normal laptop. It wraps into a grid instead, and the rule that carried the
|
||||
dots moves from the container onto each item so a wrapped row still gets a
|
||||
line above it. */
|
||||
.tl{margin-top:2.6rem}
|
||||
/* Zero column gap so the borders of adjacent items meet and the rule reads as
|
||||
one continuous line; the items carry their own right padding instead. */
|
||||
.tl__t{display:grid;grid-template-columns:repeat(auto-fit,minmax(13.5rem,1fr));gap:1.7rem 0}
|
||||
.tl__i{padding:1.4rem 1.3rem 0 0;position:relative;border-top:1px solid var(--line2)}
|
||||
.tl__i::before{content:"";position:absolute;left:0;top:0;width:7px;height:7px;border-radius:50%;
|
||||
background:var(--cyan);transform:translateY(-50%) scale(0);transition:transform .45s cubic-bezier(.16,1,.3,1)}
|
||||
.in .tl__i::before{transform:translateY(-50%) scale(1)}
|
||||
.tl__i:nth-child(1)::before{transition-delay:.05s}.tl__i:nth-child(2)::before{transition-delay:.13s}
|
||||
@@ -442,14 +457,16 @@ footer{background:var(--deep);border-top:1px solid var(--line2);padding:3rem 0 4
|
||||
.eco__note{color:var(--dim);font-size:.92rem;max-width:66ch;margin-top:.7rem}
|
||||
.eco__note a{color:var(--cyan);text-decoration:underline;text-underline-offset:.2rem}
|
||||
.eco__aside{color:var(--faint);font-size:.85rem;margin-top:1rem;max-width:66ch}
|
||||
.eco__aside a{color:var(--cyan);text-decoration:underline;text-underline-offset:.2rem}
|
||||
|
||||
/* Employers are set as a running sentence, not a grid of pills, so the line
|
||||
reads as a statement about people rather than a wall of customer logos. The
|
||||
names take weight and the connective text stays dim, which lets the eye
|
||||
scan the list without the paragraph turning into a badge rack. */
|
||||
.eco__names{color:var(--dim);font-size:1rem;line-height:1.75;max-width:62ch;margin-top:1rem}
|
||||
.eco__names b{color:var(--ink);font-weight:600}
|
||||
|
||||
.eco{list-style:none;padding:0;margin:1.4rem 0 0}
|
||||
.eco--co{display:flex;flex-wrap:wrap;gap:.5rem}
|
||||
.eco--co li{display:flex;align-items:baseline;gap:.5rem;padding:.5rem .8rem;border-radius:999px;
|
||||
border:1px solid var(--line);background:rgba(255,255,255,.028);transition:border-color .22s,background .22s}
|
||||
.eco--co li:hover{border-color:rgba(95,205,228,.5);background:rgba(95,205,228,.07)}
|
||||
.eco--co b{font-size:.9rem;font-weight:600;color:var(--ink)}
|
||||
.eco--co span{font-family:'Geist Mono',monospace;font-size:.6rem;color:var(--faint);white-space:nowrap}
|
||||
|
||||
.eco--int{display:grid;gap:.8rem}
|
||||
@media(min-width:700px){.eco--int{grid-template-columns:repeat(2,1fr)}}
|
||||
|
||||
Reference in New Issue
Block a user