662 Commits
Author SHA1 Message Date
Timothy Jaeryang Baek 079fb19e75 refac 2026-10-16 18:58:58 +04:00
Timothy Jaeryang Baek 92e64c1d59 refac 2026-10-16 14:31:39 +04:00
Timothy Jaeryang Baek 0b3812f285 refac 2026-10-15 17:56:56 +04:00
Timothy Jaeryang Baek 7dc91de86c refac 2026-10-15 17:08:00 +04:00
Classic298 3bc82bb2c1 feat: allow a custom TLS certificate for Milvus connections (#32427)
Open WebUI could not connect to a Milvus server that uses a self-signed or company-issued TLS certificate, because there was no way to tell it which certificate to trust. The new MILVUS_SERVER_PEM_PATH setting takes the path to that certificate file and uses it for the Milvus connection, in both normal and multi-tenancy mode. Leaving it unset keeps the current behaviour.

Fixes #31632
2026-10-14 23:59:57 +04:00
Timothy Jaeryang Baek c0f56767f5 refac 2026-10-14 19:44:26 +04:00
Timothy Jaeryang Baek 00f12c0e47 refac 2026-10-14 18:49:51 +04:00
Timothy Jaeryang Baek 3ec0774503 refac 2026-10-14 17:49:31 +04:00
Timothy Jaeryang Baek 7ae0996745 refac 2026-10-13 00:29:26 +04:00
Timothy Jaeryang Baek 04bccecb8e refac 2026-10-12 17:29:04 +04:00
Timothy Jaeryang BaekandTaylor Wilsdon 088830cf82 feat: Dynamic Tool Loading / Lazy Loading
Co-Authored-By: Taylor Wilsdon <6508528+taylorwilsdon@users.noreply.github.com>
2026-10-10 22:43:52 +04:00
Timothy Jaeryang Baek 0c3f74c9c1 refac 2026-10-10 20:41:13 +04:00
Timothy Jaeryang Baek 0ffd86967f refac 2026-10-08 23:21:57 +04:00
Timothy Jaeryang Baek 91853205e8 refac 2026-10-08 23:06:53 +04:00
Timothy Jaeryang Baek 6defd4a947 refac 2026-10-07 16:17:22 +04:00
Timothy Jaeryang Baek 0f5a58f5fb refac 2026-10-07 01:02:33 +04:00
Timothy Jaeryang Baek fa4c7fe5e8 refac 2026-10-07 00:57:06 +04:00
Timothy Jaeryang Baek 093bfce2b6 refac 2026-10-07 00:39:16 +04:00
Timothy Jaeryang Baek b0bcd94519 refac 2026-10-06 16:41:33 +04:00
Timothy Jaeryang Baek 24e30d1cbd refac 2026-10-05 12:17:25 +04:00
Timothy Jaeryang Baek 77e6bc2893 refac 2026-10-05 09:56:10 +04:00
Classic298 6e5e5fe6e9 feat: add a Tavily search depth setting (#31308)
Tavily web search always ran at Tavily's default depth (basic), because the search request never sent `search_depth`. The only Tavily depth control in Admin > Settings > Web Search, "Tavily Extract Depth", applies to the Extract API used by the web loader, never to search.

This adds `TAVILY_SEARCH_DEPTH` (env var and persisted setting, default `basic`) and a "Tavily Search Depth" select (ultra-fast, fast, basic, advanced) under the Tavily search engine settings. The value is sent as `search_depth` on every Tavily search request, so admins can set search and extract depth independently, for example fast search with advanced extraction.

The default matches Tavily's own default, so existing setups keep the same behaviour until the setting is changed.

Fixes #29891
2026-09-27 23:15:09 +04:00
Timothy Jaeryang Baek 30881dbcc9 refac 2026-09-21 11:09:09 -04:00
Classic298 3d29548716 fix: pgvector reads leak their connection and lose most of their neighbours (#30142)
Two defects on the pgvector read path. A search, query or get that finds nothing returns before the rollback that ends its read-only transaction, so the session keeps the connection it checked out; retrieval fans out over worker threads and the session is thread-local, so every thread that runs an empty read holds a connection for the lifetime of that thread. Users see vector search die after a while with "QueuePool limit of size 5 overflow 10 reached" and no way back other than a restart. Rolling back before the three early returns puts the connection back: measured on PostgreSQL 16, twelve empty reads on a default-shaped pool left all 12 connections checked out before and 0 after.

All collections also share one table under one vector index, and the WHERE collection_name filter is applied after the index walk, so a knowledge base holding a small share of the rows keeps only a small share of its neighbours, silently. pgvector 0.8 added iterative scans for this: the scan keeps going until enough rows pass the filter. This sets it per search so it cannot leak into other sessions, and only when the installed extension supports it, since setting it on pgvector 0.7 would make every search raise. PGVECTOR_ITERATIVE_SCAN turns it off or picks strict_order; it defaults to relaxed_order because the shipped behaviour is silently wrong results, and the cost is about a millisecond per search.

Measured through PgvectorClient.search on PostgreSQL 17 and pgvector 0.8, 101500 rows of 384 dimensions with the knowledge base at 1.5% of the table, hnsw m=16, recall@10 against an exact scan over 40 queries: 0.070 at 4.9 ms before, 0.970 at 7.2 ms after.

Fixes #30133
Fixes #30135
2026-09-18 19:31:49 -04:00
Classic298 9ee810ad09 feat: add Staan as a native web search provider (#30138)
European deployments that need EU data residency have no hosted web search option out of the box: every plug-and-play provider shipped today is US-based, and the only sovereign alternative is self-hosting SearXNG, which means running and maintaining that infrastructure yourself. Staan (staan.ai) is a European search API with EU data residency, so adding it gives those deployments a drop-in choice.

It is configured like any other provider, through the admin UI or STAAN_API_KEY, STAAN_MARKET and STAAN_MAX_SNIPPETS. Market sets the region and language of the results and defaults to en-us. Max snippets asks Staan to fetch each result page and return semantically scored chunks of it, which get merged into that result's snippet so retrieval has more to work with; leaving it at 0 uses the plain search endpoint.

Wired the same way as Tavily and Exa, with domain filtering going through the shared get_filtered_results.

Requested in #26006.
2026-09-18 10:38:36 -05:00
Timothy Jaeryang Baek 6d8e63e366 refac 2026-09-16 00:02:48 -04:00
Timothy Jaeryang Baek 12b14124b9 refac 2026-09-08 23:18:48 -04:00
Timothy Jaeryang Baek d27aa72ab4 refac 2026-09-06 17:13:32 -04:00
Timothy Jaeryang Baek cb942bb94c refac 2026-09-06 16:48:30 -04:00
Timothy Jaeryang Baek 67ac1a4e93 refac 2026-09-04 19:12:41 -04:00
Timothy Jaeryang Baek 858ab727d2 refac 2026-09-04 18:31:59 -04:00
Timothy Jaeryang Baek 7b6562e335 refac 2026-09-04 17:39:34 -04:00
Timothy Jaeryang Baek 8c0c7b3b6c refac 2026-08-31 10:19:05 -04:00
Classic298 e3e4bd87df refac: consolidate the web fetch address checks onto the request path (#27823)
* fix: apply the SSRF checks to redirect targets on every web fetch path

Two guards protect server-side fetches: a private-IP check and the operator's `WEB_FETCH_FILTER_LIST`. Neither reached a redirect hop on the aiohttp paths, and the filter list never reached one on the requests paths either.

aiohttp answers IP-literal hosts itself without consulting a resolver, so `_SSRFSafeResolver` was never invoked for a hop such as `http://169.254.169.254/` and the private-IP check simply did not run. With redirect following enabled, a submitted public URL that redirects to an IP literal reached loopback, RFC1918 and cloud-metadata addresses, and the response body was returned to the caller. The filter list was consulted only in `validate_url`, on the originally submitted URL, so a redirect to a filter-listed host was fetched without it ever being applied.

`_SSRFSafeResolver` is replaced by `_SSRFSafeConnector`, which hooks `_resolve_host` so the IP check also covers the IP-literal shortcut and both DNS cache paths. The filter list moves to a per-request hook on each transport, `connect()` for aiohttp and `send()` for the requests adapter, because those see the request destination: at the connection layer a proxied request presents the proxy's host, and a pooled connection skips resolution entirely. This covers every hop, including redirects, on all five aiohttp call sites and both requests sessions. The Playwright loader already validated each hop and is unchanged.

Both gaps required `AIOHTTP_CLIENT_ALLOW_REDIRECTS=true`, which is not the default.

Two behaviour changes for operators. The filter list now applies to redirect targets rather than only to submitted URLs. Under a forward proxy it is evaluated against the request destination instead of the proxy, which also fixes allowlist entries rejecting every fetch in proxied deployments.

* refac: match the web fetch filter list against resolved addresses

The filter list is now evaluated against the hostname together with the addresses it resolves to, at URL validation and on each connection, on both transports. An IPv6 address is also matched by the IPv4 address it carries.

* refac: screen outbound fetch addresses against reserved ranges ipaddress misses

`ipaddress.is_global` was the only test behind the web-fetch address check, and it answers a narrower question than "may we fetch this". Several special-purpose ranges are globally routable by registry while nothing on them is a legitimate destination, so they passed. Classification now screens those ranges on top of `is_global`, and applies the same screen to the IPv4 address embedded in an IPv6 transition encoding rather than only to the literal. All three checkpoints share the predicate, so they all inherit it.

The range list is the exact complement of what CPython's `ipaddress` already models, checked entry by entry against both IANA special-purpose registries. Prefixes IANA marks globally reachable are deliberately left out, so no real destination changes behaviour. Verified against 31 addresses covering every entry, their transition-encoded forms, and public controls in both families: 31/31 expected after, 18/31 before.

* refac: match web fetch filter entries that name an address or a range

A filter entry that parses as an address or a CIDR range is matched by containment rather than by DNS label suffix, so a range covers the addresses inside it and an address matches however it is spelled. A range entry previously matched nothing at all, silently.

The built-in list gains the special-purpose networks that ipaddress.is_global reports as reachable while nothing on them is a legitimate destination, so taking an address out of reach is a WEB_FETCH_FILTER_LIST change rather than a release. Those entries hold whether or not local web fetch is enabled; the private-address rule still follows the toggle.
2026-08-25 11:15:48 -04:00
Timothy Jaeryang Baek 170ad0595d refac 2026-08-24 19:48:49 -04:00
Timothy Jaeryang Baek 363ad352fe refac 2026-08-24 17:12:56 -04:00
Timothy Jaeryang Baek 7d4747dfd7 refac 2026-08-20 13:13:51 -07:00
Timothy Jaeryang BaekandClassic298 4ec6ee1441 refac
Co-Authored-By: Classic298 <27028174+Classic298@users.noreply.github.com>
2026-08-17 00:47:32 -07:00
Timothy Jaeryang Baek 7d99b2716a refac 2026-08-13 19:59:11 -06:00
Timothy Jaeryang Baek 5c05608e3a refac 2026-08-13 15:02:18 -06:00
Timothy Jaeryang Baek f0bfcd4097 refac 2026-08-11 01:15:05 -06:00
Timothy Jaeryang Baek ec03e88144 refac 2026-08-10 19:08:46 -06:00
Timothy Jaeryang Baek b5f86e6a43 refac 2026-08-10 19:08:06 -06:00
Timothy Jaeryang Baek 37f2548155 refac 2026-08-10 18:46:36 -06:00
Classic298 2d18727ab8 perf: build info log messages lazily so raising the log level actually saves work (#27837)
Raising GLOBAL_LOG_LEVEL to WARNING buys quieter output but not less work: 241 INFO call sites interpolate their payload into an f-string before the logging call gets to drop it. The heaviest is get_doc, which logs every chunk id and metadata dict in a collection, so on the full-context retrieval path that is the entire knowledge base, once per chat request.

That one line at WARNING, CPython 3.12:

| knowledge base | payload | before   | after   |
| -------------- | ------- | -------- | ------- |
| top-k of 3     | 1.2 kB  | 3.8 us   | 0.07 us |
| 500 chunks     | 201 kB  | 583.6 us | 0.08 us |
| 5000 chunks    | 2.0 MB  | 5.8 ms   | 0.15 us |

The lazy form log.info('query_doc:result %s %s', result.ids, result.metadatas) hands the payload to record.getMessage(), which the InterceptHandler only reaches once a record has passed the level check. Output at INFO is byte-identical. Two sites that already built their message eagerly, one str concat and one % operator, move to the same lazy form.
2026-08-02 15:39:10 -05:00
Timothy Jaeryang Baek bb0f898b43 refac 2026-07-31 17:41:14 -04:00
Timothy Jaeryang Baek 810378c0b8 refac 2026-07-27 19:39:36 -04:00
Timothy Jaeryang Baek b6b16d5871 refac 2026-07-27 19:24:03 -04:00
Timothy Jaeryang Baek c004b4ecb5 chore: format 2026-07-27 04:38:46 -04:00
Timothy Jaeryang Baek db2d24896b refac 2026-07-27 03:32:21 -04:00