Repository navigation
Conversation
…uckGo default, SearXNG, Brave, Tavily, Serper), composer globe toggle, results injected as context, sources under each answer and in the Markdown export - serve/websearch.py: stdlib-only engine wrappers; SearchError carries a readable message - POST /search: every request carries its own engine config, nothing (no keys) is stored on the server - keys stay in the browser's localStorage; a failed search leaves a persistent warning on the message
…page-text limit switch
…c), not the HTTP handler
…ces, DDG bot-check fix, clearer errors, Settings wording and empty-state text
…too: reason' instead of two bare parenthesised reasons
… for engine base and page reading, links rendered only for http(s), search prompt marks web data untrusted, LAN-bound servers warn when search turns on
…d-as transparency in the sources line, Chat Settings drawer rework (fixed Reset/Apply, Sampling section, search controls collapse when off), live search progress in the pill and answer bubble with a single pill priority function so the metrics poll and the search timer can no longer fight
…s, optimizer, page reading, sources, hardening)
…wo download notes
…telist, engine whitelist, reader tiers, reading budget)
…s as upstream's App)
…the now-stale engine enumeration
…losed picker shows only the default; the list described the section's offers), extended to all 7 engines
… snippet caps) Independent dsh (DeepSeek harness) review findings pinned as tests: - _result_rows: keyed engines reject malformed envelopes with a named SearchError instead of AttributeError; junk rows dropped - highlights/citations type guards (list-only), Exa text fallback - SNIPPET_CAP 2000 applied to every engine, not just Exa/Perplexity - suite: 27 tests, all offline
Author
|
No behavior change for well-formed responses. Manual re-check of DuckDuckGo and SearXNG searches in the live web app today: results and sources unchanged. |
…DMEs restyled by upstream, fork callout and search screenshots re-inserted above Models
Owner
|
Thank you for the very complete work on web search. Sending web searches out of the PC needs a privacy and safety design first, so this is planned for a later release, with no date yet. This will stay open until then. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem and result
The Chat tab of the lean web app (the serve/Terminal frontend, not the App) has no web access at all: answers are limited to the model's weights, and agent-style use on a headless server had no source-backed option. Upstream shipped web search for the App in 0.2.0; the serve web chat route is still without it.
Before: no search in the web app's Chat, no sources. After: a built-in web search, modeled after how Open WebUI and similar chat tools do it: the model can look the question up before answering, and the answer shows its sources. Web search is completely off by default: until the user turns it on in Chat Settings, nothing about this feature sends anything out of the PC. Strata's Chat is deliberately lean, and this feature is built the same way: it adds no tokens until a chat turns search on, and results are budget-capped to a small slice of the model's context window (defaults scale with the context window), so the Chat gains recent information without losing what makes it comfortable for long sessions.
Related discussion
Replaces #2826: that PR was closed in the pre-0.2.0-beta tracker reset ("closed here does not mean rejected"); after the rebase its stored head commit became unreachable, so GitHub refuses to reopen it and this PR carries the same work on a live base. Upstream's App search (
apps/host, DuckDuckGo via MCP) does not overlap with this work: this targets the serve web chat, with self-hostable engines (SearXNG) and server-side page reading.Status: maintained & up to date
This branch is kept current with upstream 0.2.2 (engine 0.1.47) and re-tested after every refresh. On real hardware (Ryzen 9 7950X3D, RTX 4080 SUPER, Windows 11) the committed offline suite is green on the merged tree (27 tests: scheme guards, engine whitelist, reader tiers, keyed engines, context-aware budgets), and live DuckDuckGo and SearXNG searches still return results and sources unchanged. The fork
web-searchbranch is pushed to the current 0.2.2 state, so this PR merges cleanly against latestmain.Approach
Search runs server-side in the serve backend (
serve/websearch.py, stdlib-only); the browser frontend only configures and renders it. All providers are fetched through the Strata server (a browser could not call them directly because of CORS). API keys stay browser-only (localStorage), like the existing API-key field; nothing search-related is stored on the server.What it looks like in the UI
[n].Sources (DuckDuckGo): [1] … | [2] …(clickable, engine named, read pages get areadbadge), and they are included in the "Save this chat as Markdown" export.Framing query→Searching web + reading pages · DuckDuckGo · 12.4 s; a failed search readsSearch failedfor 2.5 s. The same status line appears in the answer bubble exactly where "Thinking…" would sit.… · 1 queued) instead of hiding it or hijacking the label: one priority function owns the pill (search phase > engine state > queue count > idle), so the 1 s metrics poll and the 250 ms search timer can no longer overwrite each other.Query optimizer
Conversational wording ("can you look up what the newest Qwen release from Alibaba is") makes weak search queries, so there is an "Optimize search query" toggle (on by default): one fast call to the user's own local model (temperature 0, reasoning off, max 100 tokens) extracts the core search question before the search runs.
Sources (DuckDuckGo) · searched: "…", and so does the Markdown export. An optimized query is never hidden behind a plain-looking result.Reading result pages in full
Snippets alone often miss the details, so search can also fetch the top result pages and inject their extracted text (Open WebUI's "Web Loader" idea, stdlib
html.parserbased, which skips nav/footer/scripts, prefersmain/article):Page reader: direct fetch, or Jina Reader for JS-heavy pages
Plain
urllibcannot render JavaScript-heavy pages, so there is a 3-tier Page reader setting:STRATA_JINA_URL(e.g. a self-hosted instance), and failures of either path degrade to a readable combined reason.Context-aware reading budget
How much search text goes into the prompt scales with the model's own context window (
reported_ctx()), so small-context instances are never overloaded:/health'smax_context(e.g. 16K ctx → 4 results / 1 page × 3000 chars; 128K → 6 / 2 × 5000; 262K → 8 / 3 × 5000).Distinct, persistent search warnings
A failed search no longer fails silently: the reason stays on the message (in the sources line, warning-colored) and in the Markdown export, with a different message per cause:
Brave Search needs its API key (Chat Settings > Web search)(same for Tavily/Serper/Exa/Perplexity; a rejected key gets its own message, incl. Brave's 422)SearXNG at http://127.0.0.1:8888 is not reachable (…); start it or pick another engineanomaly.jspage; the fetcher uses an honest custom UA and, if the bot page still shows up, says so instead of reporting "no results"no resultsread_error(shown as a warning line), never a silent empty stringSecurity hardening
/searchsits behind the web app's own-page guard (_own_page): a JSON body is required (415 for form posts) and a foreignOriginis rejected (403;trusted_originsstill allowed), so a random web page cannot make this PC run searches or fetch URLs through it._http_url);file://,data:,javascript:and friends are refused with an explicit reason, so page reading can never become a local file reader.http(s)URLs become clickable<a href>; anything else shows as plain text, so nojavascript:/data:links from a poisoned result./healthreportslan_bound(server bound beyond loopback, e.g.0.0.0.0); turning search on fires an extra toast line warning that queries and engine keys then cross the network in plain HTTP and that devices on it can use the search without an api_key. On the default127.0.0.1bind this warning never appears.Settings drawer updates
Scroll fix (drive-by)
Auto-follow-to-bottom was broken for turns where content grows past the stick threshold mid-stream (the thinking panel expanding could pin the view at the user's own message). Stickiness is now captured before the content grows, so the view keeps following the answer.
Engines
http://127.0.0.1:8888)sonar)Exa and Perplexity call the same endpoints upstream's App calls (
api.exa.ai/searchwith Bearer key,api.perplexity.ai/chat/completionsmodelsonar, sources taken fromsearch_results, falling back to barecitationswhen the plan omits them), so a key someone already has for the App works here unchanged.Design notes
serve/websearch.pyis stdlib-only (urllib,json,re,html.parser,os); no new dependencies, cross-platform (Windows/Linux/macOS).POST /searchcarries its own engine config per request; nothing is stored on the server.contenton success,read_erroron failure,read_viafor Jina-read pages) instead of heuristics./searchhas a catch-all so an unexpected error returns a clean 502 with a server-side log line instead of a broken connection.Validation
Re-validated today (20.10.) on the rebased branch
web-search@0042c1e2, which contains upstreammainincluding894d1be1(0.2.1) and8aaf188a(user-args/VRAM fix). Environment: Windows 11, RTX 4080 SUPER 16 GB, Ryzen 9 7950X3D, engine 0.1.45 (MIN_ENGINE(0,1,45) met), model qwen3.8-flash-next-iq3_xxs, 128K ctx.Passed:
python -m unittest serve.test_websearch -v(the suite now committed in this branch): 27 tests, all green (scheme guard rejects 15 poison URL typesfile:// gopher:// javascript: data: ftp:// dict:// ldap:// sftp:// ssh:// mailto: telnet: jar: chrome-extension: vbscript:and accepts http(s); reader tier matrix direct/fallback/always incl. Jina rescue on a failing direct fetch and the combined both-paths-fail reason; bad reader values degrade to direct; non-HTML content refused; reading-budget clamps at 10 %/25 % of context with per-page caps derived at 4K/16K/128K;max_resultsclamp; keyed engines: Exa/Perplexity demand their key, Exa result+highlights mapping skips URL-less rows, Perplexity mapssearch_resultsand falls back to barecitations; hardening pass from an independent review pinned as tests: malformed engine envelopes (top-level lists, non-list result rows, non-dict rows, wrong-typehighlights/citations) become named engine errors instead of crashes, junk rows dropped, snippet cap 2000 verified for every engine).python -m py_compile serve/server.py serve/websearch.py serve/runconfig.py tools/strata_bench.pyon the merged revision: clean.--vram-reserve-mib 700,--pcie-frac 0.20,--pool-workers 10; no override), and 3 benchmark arms measured agent decode 104.9 / 110.7 / 110.8 tok/s, chat decode median 94.1 vs 94.6 baseline from 19.10. (no regression introduced by the merge).lan_boundverified on loopback vs.0.0.0.0mock servers; the pill priority table unit-tested headlessly.Failed: none observed.
Not run: CUDA/HIP/SYCL/Metal builds and the Docker images (this PR changes only the Python serve layer and its web frontend, no engine or C++ code); the 45 headless-JS UI checks from development were not re-run after the rebase (the committed Python suite covers the server side; the UI behaviors were verified interactively pre-rebase); Exa and Perplexity were not live-tested (no API keys available here; the request/response mapping is verified against mocks of the same endpoints upstream's App calls).
Limits
Happy to adjust anything (naming, where the settings live, engine list, budget shares, the Jina default) if this should look different.