238 Commits
Author SHA1 Message Date
G30 ab6976b5bf fix: when two tools have a function with the same name, the model can call both (#32670) 2026-10-21 02:25:31 +09:00
G30 fd1e59b25f fix: an array query parameter reaches an OpenAPI tool server as repeated values (#32677) 2026-10-21 02:18:13 +09:00
Classic298 51a4d58270 feat: tools come back on their own after all OpenAPI tool servers were unreachable (#32533)
With Redis, if every tool server was unreachable when Open WebUI loaded the tool server list, it cached an empty list and served it forever. The tools stayed missing from chats and the tool list after the servers came back, until a restart, a save in the tool server settings or a manual delete of the cached Redis key. An empty cached list is now only trusted while no OpenAPI tool server is enabled, so the next request that loads tools fetches the servers again, the same way terminal servers already behave. While an enabled server stays unreachable, each such request tries it again and can wait for its timeout.

Fixes #29573
2026-10-16 14:10:33 +04:00
Timothy Jaeryang Baek 97333a4697 refac 2026-10-13 20:04:28 +04:00
Timothy Jaeryang Baek 7840194631 refac 2026-10-13 19:51:58 +04:00
Timothy Jaeryang Baek d34f73066d refac 2026-10-13 19:10:21 +04:00
Timothy Jaeryang Baek 78613d86f7 refac 2026-10-11 15:55:57 +04:00
Timothy Jaeryang Baek c7b5edc726 refac 2026-10-10 23:50:29 +04:00
Timothy Jaeryang Baek d3acc2706d refac 2026-10-10 01:28:36 +04:00
Timothy Jaeryang Baek e6476928ee refac 2026-10-09 18:37:55 +04:00
Timothy Jaeryang Baek 9bbb95048b refac 2026-10-09 15:07:43 +04:00
Timothy Jaeryang Baek a20b622ba8 refac 2026-10-09 00:43:01 +04:00
Timothy Jaeryang Baek 93fc3fcb72 refac 2026-10-08 20:33:47 +04:00
Timothy Jaeryang Baek 55e1c44c94 refac 2026-10-07 17:18:05 +04:00
Timothy Jaeryang Baek d4c561d9f2 refac 2026-10-05 14:01:02 +04:00
Timothy Jaeryang Baek fb741ebcd2 refac 2026-10-05 10:56:06 +04:00
Timothy Jaeryang Baek f50f9e6252 refac 2026-10-01 03:31:20 +04:00
Classic298 c488f60e8b fix: temporary chats save sub-agent conversations on the server (#31573)
In a temporary chat, when the model handed a task to a sub-agent, the sub-agent's conversation with the task and its answer was saved on the server, although a temporary chat should leave nothing behind. The model is no longer offered sub-agents in temporary chats.

Fixes #31567
2026-09-30 19:05:21 +04:00
G30 702da1e471 fix: stop injecting memories and memory tools when memory is switched off (#30228) 2026-09-21 01:00:08 -04:00
Timothy Jaeryang Baek 3cda47cdb4 refac 2026-09-07 12:16:09 -04:00
Classic298 1932ca649e fix: stop sending OpenAPI tool server path and query parameters in the request body (#29717)
Tool calls to an OpenAPI tool server put every argument the model returned into the JSON request body, including the parameters that were already substituted into the URL. Servers that validate their input strictly (additionalProperties: false) answered 422 "unexpected property", so reads worked and every write through an endpoint with a path or query parameter failed.

The body is now built from the model's arguments minus the operation's declared parameters, keeping any name the requestBody schema declares as a property of its own, so an endpoint that wants the resource id in the body as well as in the path still gets it.

The filter only runs when the resolved body schema lists its properties. A free-form, composed or non-JSON body offers nothing to check a name against, so those requests go out exactly as before.

src/lib/apis/index.ts carries the same request builder for direct tool server connections and had the same bug, so it gets the same fix.

Fixes #29716
2026-09-06 17:01:26 -04:00
Timothy Jaeryang Baek 2140c189e1 refac 2026-08-31 00:05:34 -04:00
Timothy Jaeryang Baek 81b9afb731 refac 2026-08-31 00:03:51 -04:00
Timothy Jaeryang Baek 26f37426b7 refac 2026-08-30 12:09:34 -04:00
Timothy Jaeryang Baek 5c62cc0517 chore: format 2026-08-25 16:53:53 -04:00
Classic298 b1bfc18762 perf: cache the serialized builtin tool spec instead of deep-copying it per request (#28860)
Every chat request hands each builtin tool a fresh copy of its cached spec, because callers mutate what they get. That copy was a full deepcopy of a nested dict, repeated per tool per message.

The builder now caches the spec already serialized, so a request only parses it back. Parsing is what produces the independent tree callers mutate, and the cached value becomes an immutable string, so a request can no longer reach the cached object at all.

Measured on CPython 3.12 with a 1.1 KB spec and 20 builtin tools per request:

| | before | after |
|---|---|---|
| stdlib json, the default | 276.2 us | 66.4 us |
| orjson | 279.5 us | 37.5 us |

Builtin specs are plain JSON by construction: pydantic normalizes every default before it reaches the schema, so a tuple, set, enum or datetime cannot appear in one, and an unserializable default is dropped rather than embedded.
2026-08-25 16:00:34 -04:00
Timothy Jaeryang Baek 97466deea1 refac 2026-08-24 18:38:29 -04:00
Timothy Jaeryang Baek 6cb2449ab7 refac 2026-08-24 18:10:08 -04:00
Timothy Jaeryang Baek cf4ac9c8db refac 2026-08-24 18:07:03 -04:00
Timothy Jaeryang Baek fd8cc2ba4a refac 2026-08-24 18:06:59 -04:00
G30 fd7024f198 fix: log tool server connectivity failures without a traceback (#27757) 2026-08-24 07:35:57 -04:00
Timothy Jaeryang Baek f64c0c87e8 refac 2026-08-23 15:14:43 -04:00
Timothy Jaeryang Baek 78f48a21ee refac 2026-08-23 14:40:48 -04:00
Classic298 284da2ae49 perf: reuse the already loaded chat when assembling builtin tools (#28809)
Assembling the builtin tools for a chat message fetched the chat row a second time to answer one question: whether this is a note chat. The caller had loaded that same row a few lines earlier, from the same id in the same metadata dict, and had already evaluated the same predicate for its own note handling. So every message with builtin tools enabled read the whole conversation blob twice.

The caller now works the flag out once and passes it down. Tool assembly no longer touches a chat model at all, so the two files cannot drift apart when the shape of that metadata changes.

Measured with a stub request across five chat shapes, a note chat, a plain chat, an internal chat that is not a note, a chat id with no row behind it, and an unsaved chat id: the returned tool set is identical in every case and the query count drops from six to five. The note tools are still enabled for a note chat with the notes feature switched off, which is the only thing that predicate decides.
2026-08-19 11:06:49 -07:00
Timothy Jaeryang Baek f1a64ccfc2 refac 2026-08-17 00:35:12 -07:00
Classic298 cd9db21c52 refac: bind tool server cookies per connection (#28630)
The tool callable now takes its connection's cookie jar as a parameter, matching how its headers are already passed and how the terminal tool factory in the same module builds its callables.
2026-08-17 00:25:07 -06:00
Timothy Jaeryang Baek 083e351441 refac 2026-08-13 21:02:17 -06:00
Timothy Jaeryang Baek 4465f52a3e refac 2026-08-13 18:13:43 -06:00
Timothy Jaeryang Baek b606e13da3 refac 2026-08-10 19:44:56 -06:00
Timothy Jaeryang Baek 009999f363 refac 2026-08-08 15:47:10 -06:00
Classic298 52cfb02c72 perf: build debug log messages lazily so disabled debug logs cost nothing (#27834)
GLOBAL_LOG_LEVEL defaults to INFO, so every log.debug(...) in the backend is discarded, but the message is built first: 187 call sites interpolate their payload into an f-string before the logging call runs, so the work happens on every request and the result is thrown away. The worst one sits in process_chat_payload and stringifies the whole request body, full conversation history included, once per chat completion.

That one line with DEBUG disabled, CPython 3.12:

| conversation | payload | before   | after   |
| ------------ | ------- | -------- | ------- |
| 4 messages   | 1.2 kB  | 3.4 us   | 0.07 us |
| 20 messages  | 17 kB   | 24.8 us  | 0.07 us |
| 60 messages  | 123 kB  | 216.6 us | 0.07 us |

The lazy form log.debug('form_data: %s', form_data) hands the payload to record.getMessage(), which the InterceptHandler only reaches once a record has passed the level check. With DEBUG enabled the emitted lines are byte-identical, f'{x=}' sites included: those map to %r. MistralLoader._debug_log callers get the same treatment, since that wrapper already forwards *args.
2026-07-31 19:09:01 -05:00
Timothy Jaeryang Baek bb0f898b43 refac 2026-07-31 17:41:14 -04:00
Timothy Jaeryang Baek 7537989235 refac 2026-07-27 03:41:08 -04:00
Classic298andClaude bb928b0dfe fix: fetch the terminal system prompt per request (#27242)
* fix: fetch terminal system prompt per request with TTL cache

The system prompt was only fetched once in set_terminal_servers (startup
or connection save) with a 3s timeout, using a synthetic 'system' user.
That snapshot silently stays empty when the fetch races a cold-started
orchestrator instance, and goes stale when instances are reprovisioned
with a changed OPEN_TERMINAL_SYSTEM_PROMPT — recovering only after a
restart or a manual connection re-save.

- Fetch /system during get_terminal_tools with the user's own
  credentials via a central TTL-cached method (5 min per server+user;
  failures cached 60s so a dead instance doesn't stall every request),
  falling back to the cached snapshot.
- Raise the fetch timeout from 3s to 30s so cold-provisioned instances
  can answer.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KjnQJNKozp47vTB13pRyYs

* refac: fetch the terminal system prompt per request without a cache

Drop the module-level TTL cache and fetch the system prompt directly in
get_terminal_tools, gathered with the existing uncached per-request cwd
fetch that already follows this pattern. The fetch uses the user's own
credentials and falls back to the set_terminal_servers snapshot, so a
cold or unreachable instance degrades to the previous behaviour instead
of needing an error cache. Also restore the 3s timeout: on the request
path a 30s wait would stall chat completions, and a cold instance is
covered by the snapshot fallback until it warms up.

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-27 03:07:47 -04:00
Timothy Jaeryang Baek 12974c9e4e refac 2026-07-27 02:41:05 -04:00
Timothy Jaeryang Baek d727ee4d1f refac 2026-07-27 02:38:33 -04:00
Timothy Jaeryang Baek 55e0801dab refac 2026-07-27 00:34:25 -04:00
Timothy Jaeryang Baek 57e60423b9 refac 2026-07-27 00:27:38 -04:00
Classic298andClaude f32b19c1f6 feat: add {{USER_GROUPS}} and {{USER_GROUP_IDS}} placeholders for custom forwarded headers (#27236)
Custom per-connection headers can now forward the user's groups to
upstream backends via two new template placeholders:

- {{USER_GROUPS}}: comma-separated group names
- {{USER_GROUP_IDS}}: comma-separated group ids

The group lookup is async, so get_custom_headers becomes an async
wrapper around the sync template substitution (parse_custom_headers)
and fetches groups lazily — only when a header value actually
references a groups placeholder. The external document loader path
runs in a worker thread without an event loop, so Loader.aload
prefetches the groups before offloading and passes them through to
ExternalDocumentLoader.


Claude-Session: https://claude.ai/code/session_01EbBEfTyu8fFJmC13rnQthT

Co-authored-by: Claude <noreply@anthropic.com>
2026-07-26 23:21:26 -04:00
Timothy Jaeryang Baek d2936c880c refac 2026-07-26 21:07:27 -04:00