WeKnora
Tencent
Open-source LLM knowledge platform: turn raw documents into a queryable RAG, an autonomous reasoning agent, and a self-maintaining Wiki.
PROJECT TOPICS
INSTALL REFERENCE
dsh plugin --profile web add github:aaronlei/dsh-web-search-diy
该命令指向仓库当前默认分支;尚无绑定当前 commit 的完整验证结果。
PROJECT README
A web search provider plugin for the DeepSeek Harness (DSH) that backs the built-in web_search tool with four selectable protocols, returning structured citation sources the calling model can ground its answer on. One mode selects the protocol, and each mode keeps its own endpoint, model, credential reference, output budget, and search options.
mode |
Protocol and request | Backend |
|---|---|---|
responses (default) |
OpenAI-compatible Responses API — POST /responses with tools: [{ type: "web_search" }] |
Qwen Token Plan (the default example), OpenAI, any compatible gateway exposing the Responses surface |
anthropic-messages |
Anthropic-compatible Messages API — POST /messages with the native web_search_20250305 server tool |
DeepSeek's official Anthropic endpoint (a drop-in for the shipped provider), any Anthropic-compatible gateway |
zhipu-web-search |
Zhipu Web Search API (basic retrieval) — POST /web_search, no model turn |
Zhipu open platform; raw structured results |
zhipu-chat-search |
Zhipu Web Search in Chat (answer augmentation) — POST /chat/completions with the web_search tool |
Zhipu open platform; retrieval fused into a grounded answer |
Defaults per mode:
mode |
baseURL |
model |
apiKeyEnv |
maxOutputTokens |
|---|---|---|---|---|
responses |
https://token-plan.cn-beijing.maas.aliyuncs.com/compatible-mode/v1 |
deepseek-v4-flash-0731 |
QWEN_TOKEN_PLAN_CN_API_KEY |
4096 |
anthropic-messages |
https://api.deepseek.com/anthropic/v1 |
deepseek-flash |
DEEPSEEK_API_KEY |
65536 |
zhipu-web-search |
https://open.bigmodel.cn/api/paas/v4 |
— (no model turn) | ZAI_CODING_CN_API_KEY, then the historical ZHIPU_API_KEY |
4096 (unused) |
zhipu-chat-search |
https://open.bigmodel.cn/api/paas/v4 |
glm-5.3-flash |
ZAI_CODING_CN_API_KEY, then the historical ZHIPU_API_KEY |
4096 |
responses is the historical behavior of this plugin: endpoint, model, and credential reference are yours to swap, and the only requirement is a model that actually exposes the web_search tool on its gateway. The one extra knob is responsesReasoningEffort, which passes the OpenAI-standard reasoning.effort (low / high); it is unset by default, because a gateway that does not implement the parameter may reject the whole request — leave it unset there.
anthropic-messages speaks DeepSeek's Anthropic-compatible endpoint: baseURL gets /messages appended, the credential reference defaults to DEEPSEEK_API_KEY (the same credential the shipped provider and the conversation model use), and the model defaults to deepseek-flash, the endpoint's rolling latest Flash. The endpoint's API names are deepseek-flash and deepseek-v4-pro; a version string is not an API name (deepseek-v4.1-flash is rejected with HTTP 400). Two protocol-specific keys exist: apiVersion (the anthropic-version header, default 2023-06-01) and maxUses (max_uses, default 5). The output budget defaults to 65536 — DeepSeek's documented max_tokens default for thinking mode (8K with thinking off, 128K at reasoning_effort: max, ceiling 384K). The Anthropic protocol wants the field present, so the plugin sends that default explicitly rather than relying on server-side behavior; it is a ceiling, not a reservation, and a tight value truncates the turn and cuts the tool round short. A blank endpoint falls back to $DEEPSEEK_SEARCH_BASE_URL before the built-in default, the same override the shipped provider honors. Each Anthropic turn is also recorded on the calling session as web/deepseek-search-llm-request (endpoint, anthropic-version, body), exactly as the shipped provider records it. It speaks the same wire format as deepseek-official, so a deployment already holding that key or fronting its own Anthropic-compatible gateway needs no reconfiguration.
Thinking is not tunable by effort or budget on that endpoint: measured against it, reasoning_effort (low/high) and thinking.budget_tokens left the thinking length unchanged (a 1024-token budget even produced more thinking than no budget), while thinking: {type: "disabled"} removed the reasoning pass entirely (same long-reasoning prompt: 38s → 8s, 9444 → 2204 output tokens). anthropicThinking therefore exposes that one switch; on ordinary search queries the latency difference is small, the saving is in thinking tokens.
*`zhipu-** modes share the Zhipu open platform basehttps://open.bigmodel.cn/api/paas/v4and the credential chainZAI_CODING_CN_API_KEY, then the historicalZHIPU_API_KEY. It leads with the name DeepSeek's own credential plane uses for a Zhipu key (azai-coding-cnmodel provider row readsZAI_CODING_CN_API_KEY), so a deployment that names its credential after that provider may leaveapiKeyEnvblank and still be found;ZHIPU_API_KEYis the name this plugin used to default to, kept only while older deployments migrate.zhipu-chat-searchdefaults its model toglm-5.3-flashwith thinking effortlow(~3.5s live-verified) — the free-tierglm-4.7-flash` is frequently rate-limited with HTTP 429 and is not the default. See the Zhipu web search docs.
The shipped DSH search provider (deepseek-official) calls DeepSeek's own Anthropic-compatible endpoint — it cannot be pointed at other gateways, and switching per deployment is hard. This plugin is a first-class DSH plugin: it registers a ctx.web search provider and overrides the shared searchProvider to it, exactly like the ecosystem's other provider plugins. Search and conversation models stay fully decoupled — use it with any chat LLM.
Why the Responses API? Many OpenAI-compatible gateways only trigger their built-in web search through the Responses API (
/responses) with an explicittools: [{type: "web_search"}]declaration — Chat Completions search flags are silently ignored there. This plugin speaks the Responses protocol and parses the structuredweb_search_callblocks'action.sourcesinto seam-standard citation sources.
Why Anthropic Messages? The shipped
deepseek-officialprovider speaks DeepSeek's Anthropic-compatible endpoint but cannot be pointed elsewhere. Theanthropic-messagesmode reproduces that protocol inside this plugin — including the dualx-api-key+Bearerheaders and the snippets joined fromcitations[].cited_text— so an existingDEEPSEEK_API_KEYdeployment keeps working while the endpoint, model, tool budget, and credential become configurable, and it inherits this plugin's dispatcher-proof response decoding.
Published on npm:
dsh plugin --profile web add dsh-web-search-diy
The bundle patch then (no manual cordis.patch.yml edits needed):
web-search-diy loader entryweb row's searchProvider to diy-search and restates fetchProvider: http (a patch replaces the whole row config)web-search-deepseek)For local development, install the checkout as a linked package (the same way other local plugins are linked):
dsh plugin --profile web add link:./dsh-web-search-diy
(replace ./dsh-web-search-diy with the actual path to your local checkout)
Note for local linked installs: the plugin declares its
@deepseek-ai/*hooks aspeerDependencies(mirrored indevDependencies). A linked package resolves its ownnode_modulesfirst, so runpnpm installinside the plugin directory once; the harness install supplies the runtime peers.
The server half is plain source at lib/index.js. The browser half's authoritative source is src/client/index.tsx, and the host consumes its built bundle at lib/client.js (the official client plugins build the same way, with tsdown), so UI changes always go through the build:
pnpm install # once: tsdown, TypeScript, React types
pnpm run bundle # one-shot build, or: pnpm run watch (rebuild on save)
pnpm run typecheck # types are checked here — tsdown only transpiles
pnpm run test:client # rebuilds first, then asserts the artifact's contracts
pnpm run test # server-side suites
lib/client.js is committed because both a linked local install and the published package ship the built artifact; the source map is a local build product and stays out of git. pnpm run test:client rebuilds before it asserts, so a forgotten build can never validate a stale artifact. scripts/verify-client.mjs loads the built bundle into a hook-faithful React stand-in — real useState/useEffect/useRef semantics and an immediately-running ctx.effect — so it fails when the artifact and the card's registration, mode-scoped fields, bucket switching, or save-control states drift apart.
The provider resolves options with precedence UI-managed file ($DSH_HOME/dsh-web-search-diy.json, written by the configuration page) > settings section / entry config > package defaults.
That file is bucketed by protocol mode:
{
"version": 2,
"mode": "anthropic-messages",
"modes": {
"anthropic-messages": { "baseURL": "...", "model": "deepseek-flash", "maxUses": 8 },
"zhipu-chat-search": { "apiKeyEnv": "ZAI_CODING_CN_API_KEY", "searchEngine": "search_pro" }
}
}
Every mode keeps its own endpoint, model, credential reference, output budget, and search options, so entering a mode restores that mode's settings — including after a page reload — instead of carrying the previous mode's values over or replacing them with canonical defaults. A mode with no bucket yet starts from that mode's official values. Top-level mode is the selected mode. Each bucket holds only the keys that mode actually uses: the page submits its whole form, so a field belonging to another mode is never written, and a leftover of one in an existing bucket is dropped on that mode's next save. Keys cleared in the page are deleted rather than stored blank, so the file only ever holds real choices, and an emptied bucket is dropped entirely. A file written by an earlier release (one flat object) is projected onto the mode it selected when read, and the next save rewrites it in the bucketed shape; reads never write. Keep a copy before a downgrade: an older plugin sees only the top-level mode and ignores the buckets.
| Key | Default | Meaning |
|---|---|---|
mode |
responses |
Protocol: responses / anthropic-messages / zhipu-web-search / zhipu-chat-search |
apiKey |
— | Literal API key; overrides apiKeyEnv when set |
apiKeyEnv |
per mode (see above) | Credential reference resolved per search via ctx.credentials; blank uses the mode's own chain |
baseURL |
per mode (see above) | API base; /responses, /messages, /web_search, or /chat/completions is appended per mode |
model |
per mode (see above) | Model served by the endpoint; zhipu-web-search has no model turn and ignores this key |
maxOutputTokens |
per mode: 65536 in anthropic-messages, 4096 elsewhere |
Output cap for one search turn (max_output_tokens in responses, max_tokens in anthropic-messages and zhipu-chat-search) |
apiVersion |
2023-06-01 |
anthropic-version header sent with each request (anthropic-messages only) |
maxUses |
5 |
Maximum web_search server-tool uses per request, sent as max_uses (anthropic-messages only) |
anthropicThinking |
default |
default sends no parameter; disabled sends thinking: {type: "disabled"} (anthropic-messages only) |
searchEngine |
search_std |
Zhipu engine: search_std / search_pro / search_pro_sogou / search_pro_quark (Zhipu modes) |
count |
10 |
Zhipu result count (1-50); a request-supplied maxResults cap takes precedence (Zhipu modes) |
searchRecencyFilter |
noLimit |
Zhipu recency window: noLimit / oneDay / oneWeek / oneMonth / oneYear (Zhipu modes) |
contentSize |
medium |
Zhipu snippet size: medium / high (Zhipu modes) |
searchDomainFilter |
— | Zhipu domain allowlist, e.g. www.example.com (Zhipu modes) |
searchIntent |
false |
Zhipu intent recognition; off searches directly (zhipu-web-search only) |
searchPrompt |
— | Zhipu chat search prompt; blank uses the official default (zhipu-chat-search only) |
reasoningEffort |
low |
Thinking effort (reasoning_effort) for the chat turn: low / high / max; low keeps thinking-only models like GLM-5.3-Flash fast (zhipu-chat-search only) |
responsesReasoningEffort |
— (unset) | OpenAI-standard reasoning.effort for the responses turn: low / high; unset sends no reasoning parameter and follows the model's own mode (responses only) |
apiKeyEnv/baseURL/modelleft empty inherit the current mode's default. Endpoint and key-reference values fossilized into a section by the old schema defaults (the Qwen ones) yield to the current mode's default when you switch modes. A DeepSeek model name fossilized the same way yields only when you switch to a zhipu mode, whose endpoint cannot serve it. A deliberate1024output budget from an old schema is likewise treated as a fossil, while a1024set through the configuration page still works. Explicitly customized values are always honored.
Every key saved from the card follows one rule: a blank value clears the key so the mode's own default applies again, an omitted key is left untouched, and an invalid value is rejected with a message naming it. apiKey is the exception — blank keeps the stored secret.
The configuration page lives in the sidebar under Plugins → dsh-web-search-diy → the web-search-diy row's Configure (registered into the plugins.row.config slot introduced in dsh 0.1.6-alpha.2; the legacy Settings → Plugins → Plugin configuration page was removed in that release). The page matches the official plugin configuration pages: edits stage locally and only Save writes, while Discard reverts to the stored values. Saving takes effect immediately — no restart. Page copy follows Settings → Language (zh / en). The API key input is write-only: leave it blank to keep the stored key.
The protocol list puts Anthropic-compatible (Messages API + web_search, used by DeepSeek official) first, while the stored default remains responses. Switching the protocol fills in the new mode's official endpoint, model, and credential reference; a value belonging to the family being left follows the switch, and a value outside every known family (a gateway token, a custom name) is kept as typed. Because entering a mode whose bucket already holds the current values would otherwise look unchanged, the selected protocol counts as a pending change on its own: the mode switch and any edited parameters are written by the same Save, and the button is disabled only when the draft matches what the host holds.
Fields appear per mode, mirroring where each value actually applies: model and maxOutputTokens are hidden in zhipu-web-search (no model turn — count governs the result size there), searchIntent is offered only in zhipu-web-search, searchPrompt and reasoningEffort only in zhipu-chat-search, and apiVersion, maxUses, and anthropicThinking only in anthropic-messages. A rejected save keeps the page with the error in the footer, where the official pages report save failures.
you ──> chat LLM
│ decides it needs live info
▼
web_search tool (model-agnostic)
│ ctx.web seam ──> diy-search provider
▼
├─ responses: POST {baseURL}/responses tools: [{ type: "web_search" }]
├─ anthropic-messages:POST {baseURL}/messages tools: [{ type: "web_search_20250305", name: "web_search", max_uses }]
├─ zhipu-web-search: POST {baseURL}/web_search (raw retrieval, no model turn)
└─ zhipu-chat-search: POST {baseURL}/chat/completions tools: [{ type: "web_search", web_search: {...} }]
│
▼
chat LLM answers grounded in the results
sources[] (url + optional title from url_citation annotations) plus the model's grounded content. A response without any web_search_call block fails loudly with WEB_PROVIDER_ERROR — never a prose-scraping fallback.web_search_20250305 server tool. web_search_tool_result blocks become deduped sources[] (url, optional title, page_age → publishedAt), and snippet is joined from the response's citations[].cited_text. The provider's own prose is deliberately not returned as content, matching the shipped provider. A result block that reports a tool failure (max_uses_exceeded, too_many_requests, …) surfaces that error code instead of an unprocessable body, and a response with no result block fails loudly with WEB_PROVIDER_ERROR.search_result[] maps directly into deduped sources[] (url + title), and a digest of up to eight title-plus-snippet entries becomes the content overview. An empty result set is a valid outcome and returns empty sources[] rather than an error.choices[0].message.content is the grounded answer (content); the tool's search_result: true declaration makes the endpoint attach source details, which are parsed defensively from the message-level or root-level web_search field into sources[]. A grounded answer without source details is still a usable result.authorization: Bearer (the Anthropic mode adds x-api-key and anthropic-version). A missing key raises WEB_PROVIDER_CREDENTIAL_MISSING; a cancelled search raises WEB_ABORTED.accept-encoding: identity. This survives dsh's cross-version global undici dispatcher (where response.json() would parse raw gzip bytes), with hard caps (8 MiB wire, 16 MiB decoded) against decompression bombs.Store the key through the web Models page / credentials service ($DSH_HOME/.credentials.yaml), or export it in the launching environment. The default reference is per mode: QWEN_TOKEN_PLAN_CN_API_KEY in responses, DEEPSEEK_API_KEY in anthropic-messages, and ZAI_CODING_CN_API_KEY (then the historical ZHIPU_API_KEY) in the Zhipu modes. The provider resolves it per search; no key is retained on the provider, and no key is written to the configuration file.
MIT — see LICENSE. Copyright (c) 2026 aaronlei.
CLASSIFICATION EVIDENCE
系统优先读取 GitHub Topics,再与站内分类词典和词根规则比对。当前命中: dsh-web-search、dsh-web-search-diy。