Compare commits

...

5 Commits

Author SHA1 Message Date
localai-org-maint-bot
bc8113a8d6 fix(gallery): persist inference defaults under parameters
Gallery installs merged family defaults at the YAML root and only re-marshaled them on the artifact path. Persist the defaults in the loader-visible parameters map for every install path while preserving authored overrides.

Assisted-by: Codex:gpt-5
2026-07-31 18:06:29 +00:00
Ettore Di Giacinto
e6b235baf2 fix(website): wrap the timeline, run integrations as a reel, fix blog cards
The timeline set six 15rem columns in a flex row with overflow-x:auto, which
needs 90rem and so scrolled sideways on any normal laptop. It is a wrapping
grid now, and the rule that carries the dots moves from the container onto
each item so a wrapped row still gets a line above it. Column gap is zero and
the items carry their own right padding, so the rule stays continuous.

Integrations move from a card grid to a reel. Any single integration is a weak
signal and the whole moving line is the strong one, so the count is doing the
argument. It pauses on hover and on keyboard focus, since the names are links.
The list grows from 8 to 26: Open WebUI, Dify, LibreChat, RAGFlow, Continue,
big-AGI, Nextcloud, Frigate, promptfoo, Mods, TypingMind, baibot, k8sgpt-
operator and others. Each was admitted only after opening that project's own
repository or docs and reading the line that names LocalAI. The ones that
failed that test are listed in the data file so nobody re-adds them.

The blog cards were hand-written, which is how one of them came to advertise
"Porting vLLM to C++", a post that does not exist, and how all three linked to
the blog index instead of an article. They range over the posts now.

The section intro used the "a changelog tells you what moved, these posts show
you what it does" shape, which is the standard machine-written antithesis. It
states what the posts contain instead, including the perplexity regression
that APEX costs, because publishing the price is the actual claim.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude Code:claude-opus-5[1m]
2026-07-31 14:55:16 +00:00
Ettore Di Giacinto
0bedc75921 fix(website): rewrite the ecosystem band, drop two false coverage links
The band led with three sentences of hedging and printed a commit count
next to each employer, so a one-commit entry beside a large name read as
weakness rather than as the modest, true claim it was. It now opens on the
contributor count, sets the employers as a sentence instead of a pill wall,
and keeps the caveat to one line. The counts stay in ecosystem.yaml, since
they are the provenance for the list and anyone re-checking it needs them.

Two "coverage" cards were not about this project. The modelslab.com piece
reviews Frikallo/parakeet.cpp, an unrelated project of the same name, and
the snailtext.app benchmark measures Parakeet through ONNX Runtime without
mentioning LocalAI at all. Both are removed, along with the contributor
card that duplicated the band's opening line.

Press was four posts from one vendor, which read as the whole of the
coverage rather than one enthusiastic outlet. SUSE collapses to a single
series entry, and Pulumi, Semaphore and Spectro Cloud join it. Each was
opened and checked against the project before being added. K8sGPT and
LlamaIndex join the integrations; both document LocalAI as a backend.

The quotes move above the lists so the section opens on its strongest
line, which is somebody else's. The hero gains a GitHub call to action,
the APEX collection link was returning 404 and is corrected, and the
footer no longer describes the site as a design mock.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude Code:claude-opus-5[1m]
2026-07-31 14:24:26 +00:00
Ettore Di Giacinto
dad4d5956a ci: move lint back to hosted runners, arc image has no make/gcc
Follow-up to 42541dd4f, which routed lint to arc-runner-set. Both of its
jobs failed there in one second (run 30637392862): the runner image has
git, curl, unzip, tar, ldd and python3, but not make, and build-scripts
additionally needs gcc because the packaging-script tests compile a
throwaway binary and inspect it with ldd.

golangci-lint needs make twice over: `make protogen-go` (which also wants
curl + unzip to fetch protoc) and `make lint` itself. So both jobs go back
to ubuntu-latest.

gh-pages.yml stays on arc-runner-set and is unaffected: it uses no make and
no C toolchain, and setup-go / actions-hugo fetch their own toolchains.

The preflight steps stay. They cost about a second on the hosted pool, and
they are what turned this into a one-second named failure instead of an
opaque one midway through a build. When the runner image gains make + gcc,
re-routing is one runs-on line per job. Any such re-route must stay
push-only: lint also runs on pull_request, and fork PRs execute untrusted
code that must not reach a persistent self-hosted runner.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude Code:claude-opus-5 [ClaudeCode]
2026-07-31 14:11:45 +00:00
Ettore Di Giacinto
42541dd4f6 ci: route site deploy and lint to the self-hosted runner
The GitHub-hosted runner pool is shared per ACCOUNT, not per repo, so a
burst in one repo starves every other. On 2026-07-31 it reached zero
scheduled jobs for 35 consecutive minutes with 39 jobs queued, while
arc-runner-set completed 12 jobs without interruption across the same
window. Actions was healthy globally (other public repos were scheduling
normally), so this is an account-level throttle we cannot fix from inside
the workflows, only route around.

Site publishing and lint are small, run on nearly every commit, and gain
nothing from waiting behind a saturated hosted queue, so both move to
arc-runner-set, the label already proven in generate_intel_image.yaml.

lint.yml is routed for PUSH ONLY, and this is the important part: that
workflow also triggers on pull_request, and a fork PR executes untrusted
contributor code. Running that on a persistent self-hosted runner would be
a real compromise vector, so anything that is not a push to mudler/LocalAI
stays on the ephemeral hosted pool. gh-pages.yml needs no such clause: it
triggers only on push-to-master and workflow_dispatch, so it never runs
pull-request code. Both carry a repository guard so forks, which have no
such runner label, fall back to hosted instead of queueing forever.

Neither workflow uses sudo or apt, and both fetch their own toolchains via
setup-go / actions-hugo. A self-hosted image can still be leaner than the
hosted one, so each lint job opens with a preflight that names the missing
tool (curl/unzip/make for protoc and lint; gcc/ldd/python3 for the
packaging-script tests) rather than failing opaquely mid-build. Reverting
is one runs-on expression per job.

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Assisted-by: Claude Code:claude-opus-5 [ClaudeCode]
2026-07-31 14:08:49 +00:00
12 changed files with 411 additions and 102 deletions

View File

@@ -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`:

View File

@@ -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

View File

@@ -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

View File

@@ -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"]))
})
})

View 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))
})
})

View File

@@ -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 {

View File

@@ -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."

View File

@@ -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

View File

@@ -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 }}">&#9733; 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 &#8599;</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 &#8599;</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&#x27;s a WIP, but just wanted something cleaner then CURL calls)</p>
<span class="post__go">On X, 2023 &#8599;</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>

View File

@@ -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>

View File

@@ -13,7 +13,7 @@
<a href="{{ .Site.Params.docsURL }}">Docs</a>
</nav>
<div class="topright">
<a class="pill" href="{{ .Site.Params.github }}/stargazers">&#9733; {{ .Site.Params.stars }}</a>
<a class="pill" href="{{ .Site.Params.github }}">&#9733; {{ .Site.Params.stars }}</a>
<a class="go-btn" href="{{ $home }}#start">Install</a>
</div>
</header>

View File

@@ -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)}}