Compare commits

...

3 Commits

Author SHA1 Message Date
localai-org-maint-bot
43e70fd030 fix(images): bundle uv for backend installs
Runtime backend installation creates Python virtual environments with uv, but the final images did not include it. Copy the multi-architecture uv binaries into the shared runtime base and verify them during the image build so minimal L4T images can reinstall backends.

Fixes #10720

Assisted-by: Codex:gpt-5
2026-07-30 00:04:16 +00:00
Tai An
efb43776ba fix(chatterbox): pin cublas12 torch/transformers and setuptools so the backend loads (fixes #11070) (#11074)
fix(chatterbox): pin cublas12 torch/transformers and setuptools so the backend loads

The cuda12-chatterbox gallery backend fails to load on a fresh install
because several deps in requirements-cublas12.txt are unpinned:

- torch/torchaudio: unlike requirements-cublas13.txt and
  requirements-cpu.txt, this file has no --extra-index-url, so pip pulls
  a wheel whose CUDA runtime (cu130) is newer than the host driver
  supports ("NVIDIA driver on your system is too old"). Add the cu124
  index and pin torch/torchaudio 2.6.0+cu124.
- transformers: resolves to 5.x, which dropped LlamaConfig.rope_theta
  that chatterbox-tts 0.3.1's T3 config still reads. Cap to <5.
- setuptools: 81+ dropped pkg_resources, which perth imports under a
  bare try/except and silently sets PerthImplicitWatermarker=None,
  making ChatterboxTTS.__init__ raise 'NoneType' object is not callable.
  Cap to <81 in requirements.txt.

Fixes #11070

Signed-off-by: Tai An <antai12232931@anaiguo.com>
Co-authored-by: Tai An <antai12232931@anaiguo.com>
Co-authored-by: localai-org-maint-bot <bot-opensource@localaisrl.com>
2026-07-30 00:23:45 +02:00
mudler's LocalAI [bot]
d8a1e3c2e4 fix(realtime): echo response.metadata on response.created and response.done (#11198)
response.create accepts a metadata map and ResponseCreateParams has carried
the field all along, but triggerResponse never copied it onto the Response it
emits, so both terminals went out with metadata omitted.

That field is the only thing tying a terminal event back to the
response.create that asked for it. Our own doc comment on ResponseCreateEvent
says so — "the metadata field is a good way to disambiguate multiple
simultaneous Responses" — and it is what makes an out-of-band response
(conversation: "none") usable at all: a client running one alongside the
spoken conversation has no way to tell its own answer from the conversation's,
so it waits for a reply it already received and gave away.

Found from the client side: a headless text turn injected into a live session
was answered correctly in about a second, and the caller still blocked until
its own two-minute timeout because it could not recognise the answer.

Carry the map on liveResponse so all three terminals (in_progress, cancelled,
completed) report it, and leave it omitted when response.create sent none.

Assisted-by: Claude:claude-opus-5 gofmt

Signed-off-by: Ettore Di Giacinto <mudler@localai.io>
Co-authored-by: Ettore Di Giacinto <mudler@localai.io>
2026-07-29 23:17:31 +02:00
6 changed files with 121 additions and 17 deletions

View File

@@ -6,12 +6,19 @@ ARG UBUNTU_CODENAME=noble
ARG APT_MIRROR=""
ARG APT_PORTS_MIRROR=""
FROM ghcr.io/astral-sh/uv:0.8.22 AS uv
FROM ${BASE_IMAGE} AS requirements
ARG APT_MIRROR
ARG APT_PORTS_MIRROR
ENV DEBIAN_FRONTEND=noninteractive
# Installed backends create their virtual environments at runtime, including in
# minimal L4T images where neither system Python nor pip is available.
COPY --from=uv /uv /uvx /usr/local/bin/
RUN uv --version
# hwdata ships /usr/share/hwdata/pci.ids. Without it, the ghw library we use
# for hardware detection cannot resolve PCI vendor IDs and fails to enumerate
# GPUs at all, so the image reports "No GPU detected" (see issue #10941).

View File

@@ -1,6 +1,7 @@
torch
torchaudio
transformers
--extra-index-url https://download.pytorch.org/whl/cu124
torch==2.6.0+cu124
torchaudio==2.6.0+cu124
transformers<5
numpy>=1.24.0,<1.26.0
# chatterbox-tts itself is installed with --no-deps in install.sh.
# These are its real runtime deps, mirroring upstream's pyproject.toml
@@ -15,4 +16,4 @@ conformer
safetensors
spacy-pkuseg
pykakasi==2.3.0
accelerate
accelerate

View File

@@ -2,5 +2,5 @@ grpcio==1.71.0
protobuf
certifi
packaging
setuptools
setuptools<81
poetry

View File

@@ -2163,6 +2163,11 @@ type liveResponse struct {
output []types.MessageItemUnion
usage backend.TokenUsage
outcome responseOutcome
// metadata is echoed back on response.created and response.done. It is the
// only thing tying a terminal event to the response.create that asked for
// it, which is what lets a client run an out-of-band response alongside the
// spoken conversation and still recognise its own answer.
metadata map[string]string
}
func (r *liveResponse) addItem(it types.MessageItemUnion) { r.output = append(r.output, it) }
@@ -2192,12 +2197,16 @@ func triggerResponse(ctx context.Context, session *Session, conv *Conversation,
// terminals the legacy code emitted (one response.done per turn, with empty
// Output/Usage) are gone; tool turns are now internal to this single response.
r := &liveResponse{id: generateUniqueID()}
if overrides != nil {
r.metadata = overrides.Metadata
}
sendEvent(t, types.ResponseCreatedEvent{
ServerEventBase: types.ServerEventBase{},
Response: types.Response{
ID: r.id,
Object: "realtime.response",
Status: types.ResponseStatusInProgress,
ID: r.id,
Object: "realtime.response",
Status: types.ResponseStatusInProgress,
Metadata: r.metadata,
},
})
@@ -2208,10 +2217,11 @@ func triggerResponse(ctx context.Context, session *Session, conv *Conversation,
sendEvent(t, types.ResponseDoneEvent{
ServerEventBase: types.ServerEventBase{},
Response: types.Response{
ID: r.id,
Object: "realtime.response",
Status: types.ResponseStatusCancelled,
Output: r.output,
ID: r.id,
Object: "realtime.response",
Status: types.ResponseStatusCancelled,
Output: r.output,
Metadata: r.metadata,
},
})
case outcomeFailed:
@@ -2221,11 +2231,12 @@ func triggerResponse(ctx context.Context, session *Session, conv *Conversation,
sendEvent(t, types.ResponseDoneEvent{
ServerEventBase: types.ServerEventBase{},
Response: types.Response{
ID: r.id,
Object: "realtime.response",
Status: types.ResponseStatusCompleted,
Output: r.output,
Usage: responseUsage(r.usage),
ID: r.id,
Object: "realtime.response",
Status: types.ResponseStatusCompleted,
Output: r.output,
Usage: responseUsage(r.usage),
Metadata: r.metadata,
},
})
}

View File

@@ -263,4 +263,64 @@ var _ = Describe("triggerResponse", func() {
Expect(done.Response.Usage.OutputTokens).To(Equal(3))
Expect(done.Response.Usage.TotalTokens).To(Equal(8))
})
// response.metadata is the only thing tying a terminal event back to the
// response.create that asked for it. Without the echo, a client running an
// out-of-band response alongside the spoken conversation cannot tell its own
// answer from the conversation's, and blocks until it times out.
It("echoes response.create metadata back on response.created and response.done", func() {
m := &fakeModel{
cfg: &config.ModelConfig{},
predictResp: backend.LLMResponse{Response: "Hi there."},
}
session := &Session{
OutputSampleRate: 24000,
ModelInterface: m,
ModelConfig: &config.ModelConfig{},
OutputModalities: []types.Modality{types.ModalityText},
}
t := &fakeTransport{}
triggerResponse(context.Background(), session, &Conversation{}, t, &types.ResponseCreateParams{
Metadata: map[string]string{"client_run": "abc123"},
})
var created *types.ResponseCreatedEvent
var done *types.ResponseDoneEvent
for i := range t.events {
switch e := t.events[i].(type) {
case types.ResponseCreatedEvent:
created = &e
case types.ResponseDoneEvent:
done = &e
}
}
Expect(created).NotTo(BeNil())
Expect(created.Response.Metadata).To(HaveKeyWithValue("client_run", "abc123"))
Expect(done).NotTo(BeNil())
Expect(done.Response.Metadata).To(HaveKeyWithValue("client_run", "abc123"))
})
// Omitted rather than sent as an empty object, matching the omitempty tag.
It("sends no metadata when response.create carried none", func() {
m := &fakeModel{
cfg: &config.ModelConfig{},
predictResp: backend.LLMResponse{Response: "Hi there."},
}
session := &Session{
OutputSampleRate: 24000,
ModelInterface: m,
ModelConfig: &config.ModelConfig{},
OutputModalities: []types.Modality{types.ModalityText},
}
t := &fakeTransport{}
triggerResponse(context.Background(), session, &Conversation{}, t, nil)
for i := range t.events {
if d, ok := t.events[i].(types.ResponseDoneEvent); ok {
Expect(d.Response.Metadata).To(BeEmpty())
}
}
})
})

View File

@@ -347,6 +347,31 @@ By default a realtime session responds with audio plus a transcript. To make the
The GA `output_modalities` wins when both are present. A response-level value overrides the session-level one, and when neither is set the session falls back to `["audio"]`.
### Out-of-band responses and `metadata`
A response can be created outside the default conversation by setting `conversation` to `none`. The reply is not added to the conversation history, which is what makes it usable for a side channel — answering a chat message or a webhook while a spoken conversation is in progress.
Because such a response arrives on the same socket as everything else, attach `metadata` to correlate it. LocalAI echoes the map back verbatim on both `response.created` and `response.done`:
```json
{"type": "response.create", "response": {
"conversation": "none",
"output_modalities": ["text"],
"metadata": {"client_run": "abc123"},
"input": [{"type": "message", "role": "user",
"content": [{"type": "input_text", "text": "Is the oven still on?"}]}]
}}
```
```json
{"type": "response.done", "response": {
"id": "resp_...", "status": "completed",
"metadata": {"client_run": "abc123"}
}}
```
Keys are strings up to 64 characters, values up to 512. A response created without `metadata` omits the field rather than sending an empty object.
## Gating a realtime pipeline with voice recognition
A pipeline realtime model can require speaker verification before it responds. Add a `voice_recognition` block under `pipeline`. When present, each committed utterance is verified against authorized speakers; unauthorized utterances are dropped before the LLM runs (no LLM call, no tool execution, no TTS). The session stays open.