@ljwei-stak/model-router-galgame 0.4.12 → 0.4.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.dsh-plugin/client.js +1 -1
- package/.dsh-plugin/index.mjs +65 -3
- package/.dsh-plugin/shared/approval-gate.mjs +108 -0
- package/.dsh-plugin/shared/web-routing.mjs +90 -0
- package/README.md +348 -20
- package/README.zh.md +309 -19
- package/cordis.patch.yml +15 -0
- package/package.json +7 -2
package/.dsh-plugin/client.js
CHANGED
|
@@ -61740,7 +61740,7 @@ var gal_scene_default = { version: 1, settings: { stageW: 1920, stageH: 1080, sh
|
|
|
61740
61740
|
var name = "gal-view";
|
|
61741
61741
|
var PROJECT_URL = "https://github.com/ljwei-stak/deepseek-harness";
|
|
61742
61742
|
var RELEASES_URL = `${PROJECT_URL}/releases`;
|
|
61743
|
-
var PLUGIN_VERSION = "0.4.
|
|
61743
|
+
var PLUGIN_VERSION = "0.4.14";
|
|
61744
61744
|
function createUpdateApi() {
|
|
61745
61745
|
const bridge = globalThis.deepSeekHarnessDesktop;
|
|
61746
61746
|
const openExternal = (url) => {
|
package/.dsh-plugin/index.mjs
CHANGED
|
@@ -15,6 +15,17 @@ import {
|
|
|
15
15
|
modLensUpstream,
|
|
16
16
|
routeThroughModLens,
|
|
17
17
|
} from './shared/modlens-routing.mjs'
|
|
18
|
+
import {
|
|
19
|
+
approvalGateStatus,
|
|
20
|
+
approvalSafetyContext,
|
|
21
|
+
decorateApprovalReason,
|
|
22
|
+
isApprovalGateReason,
|
|
23
|
+
} from './shared/approval-gate.mjs'
|
|
24
|
+
import {
|
|
25
|
+
webCapabilityForPlan,
|
|
26
|
+
webCapabilityStatus,
|
|
27
|
+
webInstruction,
|
|
28
|
+
} from './shared/web-routing.mjs'
|
|
18
29
|
|
|
19
30
|
let settingsRuntimePromise
|
|
20
31
|
let routerSettings = { ...DEFAULT_ROUTER_SETTINGS }
|
|
@@ -213,6 +224,17 @@ function stageMessage(plan, step) {
|
|
|
213
224
|
}
|
|
214
225
|
}
|
|
215
226
|
|
|
227
|
+
function webMessage(plan) {
|
|
228
|
+
const text = webInstruction(plan?.web)
|
|
229
|
+
if (text === '') return null
|
|
230
|
+
return {
|
|
231
|
+
id: newMessageId(),
|
|
232
|
+
role: 'user',
|
|
233
|
+
content: [{ type: 'text', text }],
|
|
234
|
+
source: { kind: 'plugin', plugin: name, form: 'web-capability', summary: '联网与可见浏览器策略' },
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
216
238
|
/**
|
|
217
239
|
* Persona is a final-answer context only. It is intentionally a separate
|
|
218
240
|
* message so the collaboration stages and their audit records remain free of
|
|
@@ -268,6 +290,7 @@ function analysisMessage(plan) {
|
|
|
268
290
|
`缓存计费比例:读取 ${Math.round(Number(plan.optimization?.cacheReadRatio ?? 0) * 100)}%,写入 ${Math.round(Number(plan.optimization?.cacheWriteRatio ?? 0) * 100)}%(未填写时按普通输入计费)`,
|
|
269
291
|
Number(plan.optimization?.budgetUsd ?? 0) > 0 ? `预算上限:$${Number(plan.optimization.budgetUsd).toFixed(6)};${plan.optimization.budgetExceeded ? '仍超预算,已在质量下限内尽量压缩' : '满足预算约束'}` : '',
|
|
270
292
|
`LiveBench:${plan.optimization?.liveBench?.fetchedAt ? `快照于 ${new Date(Number(plan.optimization.liveBench.fetchedAt)).toISOString()}${plan.optimization.liveBench.stale ? '(本次刷新失败,沿用上次快照)' : ''}` : '未完成联网核验,使用实验基线'}`,
|
|
293
|
+
plan.web?.needsWeb ? `联网策略:${plan.web.directBrowser ? 'Ego Browser 可见窗口优先' : 'ModSearch 搜索/抓取,失败时 Ego Browser 窗口兜底'};反爬处理:人工接管后继续` : '',
|
|
271
294
|
String(plan.reason ?? ''),
|
|
272
295
|
].filter(Boolean).join('\n')
|
|
273
296
|
return {
|
|
@@ -432,6 +455,27 @@ export function apply(ctx) {
|
|
|
432
455
|
routerSettingsPromise = registerRouterSettings(ctx)
|
|
433
456
|
}
|
|
434
457
|
const scheduleOpenCodeRepair = createOpenCodeRepairScheduler(ctx)
|
|
458
|
+
|
|
459
|
+
// dsh-approval-gate owns the actual decision. The router adds auditable
|
|
460
|
+
// stage/route context before that waterfall so multi-task escalations are
|
|
461
|
+
// visible to the gate's Flash classifier and human reviewer. The request
|
|
462
|
+
// object is borrowed by the Host approval service for this dispatch only.
|
|
463
|
+
ctx.on('approval/request', (request, next) => {
|
|
464
|
+
if (!isApprovalGateReason(request?.reason)) return next()
|
|
465
|
+
const state = request?.agent === undefined ? null : stateFor(request.agent)
|
|
466
|
+
const context = approvalSafetyContext(state, state?.lastStep)
|
|
467
|
+
const decorated = decorateApprovalReason(request.reason, context)
|
|
468
|
+
if (decorated !== request.reason) {
|
|
469
|
+
try {
|
|
470
|
+
request.reason = decorated
|
|
471
|
+
} catch {
|
|
472
|
+
// Some hosts freeze event payloads. In that case the gate still
|
|
473
|
+
// receives the original reason and remains fully fail-safe.
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return next()
|
|
477
|
+
}, { prepend: true })
|
|
478
|
+
|
|
435
479
|
ctx.commands.register({
|
|
436
480
|
name: 'router',
|
|
437
481
|
description: 'switch Model Router mode or inspect the latest routing plan',
|
|
@@ -440,7 +484,7 @@ export function apply(ctx) {
|
|
|
440
484
|
// the whole composer submission; the GAL client sends this command
|
|
441
485
|
// without image bytes so the attachment remains available for the next
|
|
442
486
|
// user turn.
|
|
443
|
-
input: { hint: 'mode collective|single | plan', images: true },
|
|
487
|
+
input: { hint: 'mode collective|single | plan | safety', images: true },
|
|
444
488
|
recordInput: true,
|
|
445
489
|
handler: ({ agent, rawInput }) => {
|
|
446
490
|
const state = stateFor(agent)
|
|
@@ -458,7 +502,13 @@ export function apply(ctx) {
|
|
|
458
502
|
if (value === 'plan' || value === '') {
|
|
459
503
|
return { kind: 'success', text: state.plan === null ? '还没有可展示的路由方案。' : JSON.stringify(state.plan) }
|
|
460
504
|
}
|
|
461
|
-
|
|
505
|
+
if (value === 'safety' || value === 'approval') {
|
|
506
|
+
return { kind: 'success', text: JSON.stringify({ ...approvalGateStatus(ctx), context: approvalSafetyContext(state, state.lastStep) }) }
|
|
507
|
+
}
|
|
508
|
+
if (value === 'web' || value === 'network') {
|
|
509
|
+
return { kind: 'success', text: JSON.stringify({ ...webCapabilityStatus(ctx), context: webCapabilityForPlan(state.taskText, state.plan) }) }
|
|
510
|
+
}
|
|
511
|
+
return { kind: 'error', text: '用法:/router mode collective、/router mode single、/router plan、/router safety 或 /router web' }
|
|
462
512
|
},
|
|
463
513
|
})
|
|
464
514
|
|
|
@@ -515,7 +565,7 @@ export function apply(ctx) {
|
|
|
515
565
|
await routerSettingsPromise
|
|
516
566
|
state.taskText = inputText(messages)
|
|
517
567
|
const liveBench = await liveBenchFor(ctx, state)
|
|
518
|
-
|
|
568
|
+
const plan = buildPlan({
|
|
519
569
|
text: state.taskText,
|
|
520
570
|
available,
|
|
521
571
|
mode: state.mode,
|
|
@@ -526,6 +576,16 @@ export function apply(ctx) {
|
|
|
526
576
|
cacheReadRatio: routerSettings.cacheReadRatio,
|
|
527
577
|
cacheWriteRatio: routerSettings.cacheWriteRatio,
|
|
528
578
|
})
|
|
579
|
+
state.plan = {
|
|
580
|
+
...plan,
|
|
581
|
+
web: webCapabilityForPlan(state.taskText, plan),
|
|
582
|
+
safety: {
|
|
583
|
+
...approvalGateStatus(ctx),
|
|
584
|
+
...approvalSafetyContext({ ...state, plan }, Number(step)),
|
|
585
|
+
hardCategories: ['deletion', 'credential', 'remote', 'system', 'bulk'],
|
|
586
|
+
failSafe: true,
|
|
587
|
+
},
|
|
588
|
+
}
|
|
529
589
|
state.collaboration = shouldCollaborate(state.plan, available)
|
|
530
590
|
? { lastStep: 0, queuedStep: null }
|
|
531
591
|
: null
|
|
@@ -538,11 +598,13 @@ export function apply(ctx) {
|
|
|
538
598
|
const currentStep = Number.isFinite(Number(step)) ? Number(step) : state.lastStep + 1
|
|
539
599
|
const stageContext = stageMessage(state.plan, currentStep)
|
|
540
600
|
const analysisContext = currentStep === 1 ? analysisMessage(state.plan) : null
|
|
601
|
+
const webContext = currentStep === 1 ? webMessage(state.plan) : null
|
|
541
602
|
const hasPersona = proposed.messages.some(message => message?.content?.some(block => isPersonaPrompt(block?.text)))
|
|
542
603
|
if (hasPersona) state.personaInjected = true
|
|
543
604
|
const personaContext = state.personaInjected ? null : personaMessage(state, agent, currentStep)
|
|
544
605
|
const additions = []
|
|
545
606
|
if (analysisContext !== null && !hasStageMarker(proposed.messages, currentStep)) additions.push(analysisContext)
|
|
607
|
+
if (webContext !== null && !proposed.messages.some(message => message?.content?.some(block => block?.text === webContext.content[0].text))) additions.push(webContext)
|
|
546
608
|
if (personaContext !== null) {
|
|
547
609
|
additions.push(personaContext)
|
|
548
610
|
state.personaInjected = true
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compatibility bridge for dsh-approval-gate.
|
|
3
|
+
*
|
|
4
|
+
* The approval-gate package owns the approval waterfall, Flash judgement,
|
|
5
|
+
* learning, audit files, snapshots and UI. This module only produces a small,
|
|
6
|
+
* deterministic safety context for the current Model Router work package.
|
|
7
|
+
* Keeping the bridge stateless makes it safe when the gate is installed by
|
|
8
|
+
* another profile layer as well as when it is bundled by this plugin.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export const APPROVAL_GATE_PACKAGE = 'dsh-approval-gate'
|
|
12
|
+
export const APPROVAL_GATE_VERSION = '0.5.0'
|
|
13
|
+
|
|
14
|
+
const ESCALATION_RE = /escalate\s+sandbox\s+to\s+([^\s:]+):?\s*([\s\S]*)/i
|
|
15
|
+
|
|
16
|
+
function clean(value, max = 240) {
|
|
17
|
+
return String(value ?? '').replace(/[\r\n]+/g, ' ').trim().slice(0, max)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function stageFor(state, step) {
|
|
21
|
+
const plan = state?.plan
|
|
22
|
+
const tasks = Array.isArray(plan?.subtasks) ? plan.subtasks : []
|
|
23
|
+
if (tasks.length === 0) return null
|
|
24
|
+
const index = Math.max(0, Number(step || state?.lastStep || 1) - 1)
|
|
25
|
+
return tasks[index] ?? tasks[tasks.length - 1] ?? null
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Return the router safety facts that are relevant to an approval request.
|
|
30
|
+
* `bulk` is a candidate supplied as context; dsh-approval-gate still performs
|
|
31
|
+
* the authoritative category decision and fail-safe human handoff.
|
|
32
|
+
*/
|
|
33
|
+
export function approvalSafetyContext(state, step) {
|
|
34
|
+
const plan = state?.plan
|
|
35
|
+
const tasks = Array.isArray(plan?.subtasks) ? plan.subtasks : []
|
|
36
|
+
const stage = stageFor(state, step)
|
|
37
|
+
const collective = state?.mode === 'collective'
|
|
38
|
+
const multiTask = collective && plan?.complexity?.band === 'complex' && tasks.length >= 3
|
|
39
|
+
return {
|
|
40
|
+
mode: state?.mode ?? 'collective',
|
|
41
|
+
complexity: plan?.complexity?.band ?? 'unknown',
|
|
42
|
+
stage: stage?.id ?? null,
|
|
43
|
+
stagePurpose: stage?.purpose ?? null,
|
|
44
|
+
stageType: stage?.type ?? null,
|
|
45
|
+
stageIndex: tasks.length === 0 ? 0 : Math.max(1, Number(step || state?.lastStep || 1)),
|
|
46
|
+
stageCount: tasks.length,
|
|
47
|
+
multiTask,
|
|
48
|
+
candidateCategory: multiTask ? 'bulk' : 'neutral',
|
|
49
|
+
selectedRoute: plan?.selected?.provider && plan?.selected?.model
|
|
50
|
+
? `${plan.selected.provider}/${plan.selected.model}`
|
|
51
|
+
: null,
|
|
52
|
+
activeRoute: state?.lastTarget?.provider && state?.lastTarget?.model
|
|
53
|
+
? `${state.lastTarget.provider}/${state.lastTarget.model}`
|
|
54
|
+
: null,
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Decorate the justification consumed by dsh-approval-gate. The original
|
|
60
|
+
* escalation prefix remains intact, so the target plugin can parse it. The
|
|
61
|
+
* marker is deliberately plain text because the target plugin's Flash model
|
|
62
|
+
* judges only the justification string.
|
|
63
|
+
*/
|
|
64
|
+
export function decorateApprovalReason(reason, context) {
|
|
65
|
+
const raw = String(reason ?? '')
|
|
66
|
+
const match = raw.match(ESCALATION_RE)
|
|
67
|
+
if (!match || context === null || context === undefined) return raw
|
|
68
|
+
const mode = clean(match[1], 64)
|
|
69
|
+
const justification = clean(match[2], 500)
|
|
70
|
+
const stage = context.stage ? `${context.stage} ${context.stageIndex}/${context.stageCount}` : 'unknown'
|
|
71
|
+
const route = context.activeRoute || context.selectedRoute || 'unassigned'
|
|
72
|
+
const marker = [
|
|
73
|
+
'[model-router safety context]',
|
|
74
|
+
`mode=${context.mode}`,
|
|
75
|
+
`complexity=${context.complexity}`,
|
|
76
|
+
`stage=${stage}`,
|
|
77
|
+
`purpose=${context.stagePurpose || 'unknown'}`,
|
|
78
|
+
`route=${route}`,
|
|
79
|
+
`task_count=${context.stageCount || 0}`,
|
|
80
|
+
`risk_candidate=${context.candidateCategory}`,
|
|
81
|
+
context.multiTask ? 'multi_task_review=required' : 'multi_task_review=not_applicable',
|
|
82
|
+
].join('; ')
|
|
83
|
+
return `escalate sandbox to ${mode}: ${justification || 'router stage requires sandbox escalation'} ${marker}`.trim()
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function isApprovalGateReason(reason) {
|
|
87
|
+
return ESCALATION_RE.test(String(reason ?? ''))
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function approvalGateStatus(ctx) {
|
|
91
|
+
let approval = false
|
|
92
|
+
let permissionPresets = false
|
|
93
|
+
try {
|
|
94
|
+
approval = Boolean(ctx?.get?.('approval'))
|
|
95
|
+
permissionPresets = Boolean(ctx?.get?.('permissionPresets'))
|
|
96
|
+
} catch {
|
|
97
|
+
approval = false
|
|
98
|
+
permissionPresets = false
|
|
99
|
+
}
|
|
100
|
+
return {
|
|
101
|
+
package: APPROVAL_GATE_PACKAGE,
|
|
102
|
+
version: APPROVAL_GATE_VERSION,
|
|
103
|
+
bundled: true,
|
|
104
|
+
approvalServiceDetected: approval,
|
|
105
|
+
permissionPresetsDetected: permissionPresets,
|
|
106
|
+
policy: 'hard-risk-human-review',
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Web capability selection for the bundled ModSearch + Ego Browser pair.
|
|
3
|
+
*
|
|
4
|
+
* ModSearch owns search/fetch engines and SSRF protection. Ego Browser owns
|
|
5
|
+
* the real visible browser, login state, screenshots, and human-check handoff.
|
|
6
|
+
* This module only classifies intent and creates an auditable instruction for
|
|
7
|
+
* the model; it never fetches an untrusted URL itself.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const MODSEARCH_PACKAGE = '@liustack/modsearch'
|
|
11
|
+
export const MODSEARCH_VERSION = '5.10.1'
|
|
12
|
+
export const EGO_BROWSER_PACKAGE = 'dsh-ego-browser'
|
|
13
|
+
export const EGO_BROWSER_VERSION = '0.8.0'
|
|
14
|
+
|
|
15
|
+
const WEB_RE = /https?:\/\/|www\.|联网|上网|网页|网站|搜索|查找|资料|文献|新闻|最新|实时|网页内容|页面|来源|引用|网络|x\s*帖子|twitter|推特|github|反爬|验证码|登录|人机验证/i
|
|
16
|
+
const BROWSER_RE = /反爬|验证码|人机验证|cloudflare|turnstile|recaptcha|hcaptcha|登录|需要点击|动态页面|网页窗口|浏览器|可见窗口|手动验证|页面交互/i
|
|
17
|
+
|
|
18
|
+
function text(value, max = 12000) {
|
|
19
|
+
return String(value ?? '').slice(-max)
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function classifyWebIntent(input) {
|
|
23
|
+
const value = text(input)
|
|
24
|
+
const needsWeb = WEB_RE.test(value)
|
|
25
|
+
const directBrowser = BROWSER_RE.test(value)
|
|
26
|
+
return {
|
|
27
|
+
needsWeb,
|
|
28
|
+
directBrowser,
|
|
29
|
+
antiBotFallback: needsWeb,
|
|
30
|
+
primary: needsWeb ? 'modsearch' : 'native-model',
|
|
31
|
+
fallback: needsWeb ? 'ego-browser' : null,
|
|
32
|
+
reason: directBrowser
|
|
33
|
+
? '用户明确要求动态网页、登录态或反爬页面,优先使用可见 Ego Browser。'
|
|
34
|
+
: needsWeb
|
|
35
|
+
? '先使用 ModSearch 搜索/抓取;失败、内容不完整或触发人机验证时切换 Ego Browser。'
|
|
36
|
+
: '',
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function webCapabilityForPlan(input, plan = null) {
|
|
41
|
+
const intent = classifyWebIntent(input)
|
|
42
|
+
const tasks = Array.isArray(plan?.subtasks) ? plan.subtasks : []
|
|
43
|
+
return {
|
|
44
|
+
...intent,
|
|
45
|
+
taskCount: tasks.length,
|
|
46
|
+
stageAware: tasks.length > 1,
|
|
47
|
+
packages: {
|
|
48
|
+
search: MODSEARCH_PACKAGE,
|
|
49
|
+
browser: EGO_BROWSER_PACKAGE,
|
|
50
|
+
},
|
|
51
|
+
versions: {
|
|
52
|
+
search: MODSEARCH_VERSION,
|
|
53
|
+
browser: EGO_BROWSER_VERSION,
|
|
54
|
+
},
|
|
55
|
+
humanCheckPolicy: 'pause-and-handoff',
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function webCapabilityStatus(ctx) {
|
|
60
|
+
let web = false
|
|
61
|
+
let tools = false
|
|
62
|
+
try {
|
|
63
|
+
web = Boolean(ctx?.get?.('web'))
|
|
64
|
+
tools = Boolean(ctx?.get?.('tools'))
|
|
65
|
+
} catch {
|
|
66
|
+
web = false
|
|
67
|
+
tools = false
|
|
68
|
+
}
|
|
69
|
+
return {
|
|
70
|
+
modsearch: { package: MODSEARCH_PACKAGE, version: MODSEARCH_VERSION, bundled: true, webServiceDetected: web },
|
|
71
|
+
egoBrowser: { package: EGO_BROWSER_PACKAGE, version: EGO_BROWSER_VERSION, bundled: true, toolServiceDetected: tools },
|
|
72
|
+
antiBotWindow: 'ego_space_open -> ego_navigate -> ego_page_info/ego_captcha -> ego_snapshot/ego_screenshot',
|
|
73
|
+
humanCheck: 'pause-and-handoff',
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function webInstruction(capability) {
|
|
78
|
+
if (!capability?.needsWeb) return ''
|
|
79
|
+
const browserFirst = capability.directBrowser
|
|
80
|
+
return [
|
|
81
|
+
'[联网与可见浏览器适配]',
|
|
82
|
+
browserFirst
|
|
83
|
+
? '本任务涉及动态网页、登录态或反爬页面:优先使用 Ego Browser 的真实可见窗口。'
|
|
84
|
+
: '普通联网先使用 ModSearch 提供的 web_search/read_page;需要 X 内容时使用 x_search。',
|
|
85
|
+
'如果搜索或抓取返回 unavailable、内容不完整、JS 页面空白或 warnings,切换 Ego Browser:先 ego_space_open,再 ego_navigate;随后调用 ego_page_info 或 ego_captcha 检查 humanCheck。',
|
|
86
|
+
'页面可读时使用 ego_snapshot、ego_read_element、ego_screenshot 或 ego_http(browser) 提取证据;必须交互时使用 ego_click、ego_fill、ego_wait*。',
|
|
87
|
+
'检测到验证码、Cloudflare、Turnstile、登录或其他 humanCheck=true 时,暂停当前工作包,提示用户在 Agent 浏览器观察窗完成验证;用户确认后再继续。不要绕过验证码或伪造验证结果。',
|
|
88
|
+
'联网证据必须保留 URL、页面状态和不确定性说明;批量抓取仍遵守审批门控。',
|
|
89
|
+
].join('\n')
|
|
90
|
+
}
|
package/README.md
CHANGED
|
@@ -15,6 +15,8 @@ This plugin installs on an original DeepSeek Harness checkout. It adds a cost-aw
|
|
|
15
15
|
- **GAL view** turns each new conversation into an archive. Every line retains its actual provider/model, so the nameplate, color, and portrait follow the active model. ERNIE, Wenxin, and Baidu provider/model identifiers consistently select `ERNIE娘` and `ernie1.png`. The route explanation is an auditable summary, not private model chain-of-thought.
|
|
16
16
|
- **Markdown/KaTeX** reuses Harness `MarkdownText` for headings, lists, tables, quotes, code, links, and formulas. Wide content scrolls inside the dialogue box; player input remains plain text.
|
|
17
17
|
- **Attachments and multimodality** use the native image pipeline and extract Markdown/TXT/JSON/code as text. PDF/DOCX and other binary files keep an explicit parsing state rather than silently inventing content.
|
|
18
|
+
- **Multi-task safety approval adapter** bundles `dsh-approval-gate@0.5.0`. Every sandbox escalation in a complex collective task carries the work package, stage, route, and task-count context; multi-task work is marked as a `bulk` risk candidate for Flash classification, hard-risk human review, learning, audit, and snapshots. Single-session mode is never forcibly rerouted.
|
|
19
|
+
- **Web search and anti-bot browsing** bundle `@liustack/modsearch@5.10.1` and `dsh-ego-browser@0.8.0`. ModSearch handles `web_search`, `read_page`, and `x_search`; Ego Browser opens a real Chrome/Edge window for JavaScript pages, login state, screenshots, and human verification. Search failures and anti-bot pages are routed to the visible browser flow.
|
|
18
20
|
- **OpenCode Zen compatibility** repairs official website overrides to the catalog-owned `/zen` and `/zen/v1` endpoints while leaving custom gateways untouched.
|
|
19
21
|
- **Release updates and desktop support** provide release checks and a one-click updater. When the desktop is outdated it updates the full client and bundled plugin; otherwise it updates only the plugin. Browser-only installs open Releases because they cannot write local files.
|
|
20
22
|
|
|
@@ -29,7 +31,7 @@ This plugin installs on an original DeepSeek Harness checkout. It adds a cost-aw
|
|
|
29
31
|
|
|
30
32
|
### Recommended: install the published npm package
|
|
31
33
|
|
|
32
|
-
The package is public and already includes the official `@liustack/modlens@3.25.4`
|
|
34
|
+
The package is public and already includes the official `@liustack/modlens@3.25.4`, `dsh-approval-gate@0.5.0`, `@liustack/modsearch@5.10.1`, and the registry package `dsh-ego-browser@0.8.0` plus its runtime peers (`@deepseek-ai/dsh-tools@0.1.0-rc.8`, `schemastery@3.18.0`). You do not need to clone the Ego Browser GitHub repository or install these dependencies separately.
|
|
33
35
|
|
|
34
36
|
1. Open PowerShell in the DSH Desktop checkout:
|
|
35
37
|
|
|
@@ -75,7 +77,7 @@ pnpm dsh plugin --profile web add --registry=https://registry.npmjs.org "@ljwei-
|
|
|
75
77
|
4. Verify that the router and its ModLens dependency are present:
|
|
76
78
|
|
|
77
79
|
```powershell
|
|
78
|
-
pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens"
|
|
80
|
+
pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens|dsh-approval-gate|modsearch|ego-browser"
|
|
79
81
|
```
|
|
80
82
|
|
|
81
83
|
The output should contain:
|
|
@@ -83,6 +85,9 @@ The output should contain:
|
|
|
83
85
|
```text
|
|
84
86
|
@ljwei-stak/model-router-galgame
|
|
85
87
|
@liustack/modlens
|
|
88
|
+
dsh-approval-gate
|
|
89
|
+
@liustack/modsearch
|
|
90
|
+
dsh-ego-browser
|
|
86
91
|
```
|
|
87
92
|
|
|
88
93
|
Do not add `@liustack/modlens` separately after installing this package. The
|
|
@@ -95,6 +100,23 @@ pnpm dsh plugin --profile web remove @liustack/modlens
|
|
|
95
100
|
pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
|
|
96
101
|
```
|
|
97
102
|
|
|
103
|
+
Do not add `dsh-approval-gate` separately to the same profile. If it was previously installed as a standalone entry, remove it before reinstalling the router to avoid a duplicate loader:
|
|
104
|
+
|
|
105
|
+
```powershell
|
|
106
|
+
pnpm dsh plugin --profile web remove dsh-approval-gate
|
|
107
|
+
pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Do not add `@liustack/modsearch` or `dsh-ego-browser` separately either. If either
|
|
111
|
+
was installed before the router, remove the standalone entries and reinstall the
|
|
112
|
+
router once so each loader is registered exactly once:
|
|
113
|
+
|
|
114
|
+
```powershell
|
|
115
|
+
pnpm dsh plugin --profile web remove @liustack/modsearch
|
|
116
|
+
pnpm dsh plugin --profile web remove dsh-ego-browser
|
|
117
|
+
pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
|
|
118
|
+
```
|
|
119
|
+
|
|
98
120
|
5. Stop any already-running DSH process, then start the selected profile:
|
|
99
121
|
|
|
100
122
|
```powershell
|
|
@@ -112,7 +134,7 @@ the old process has released its profile lock:
|
|
|
112
134
|
pnpm dsh web --no-open --port 3081
|
|
113
135
|
```
|
|
114
136
|
|
|
115
|
-
### Verify
|
|
137
|
+
### Verify bundled capabilities
|
|
116
138
|
|
|
117
139
|
Run the diagnostic from the installed Web profile (the command is forwarded to
|
|
118
140
|
the profile's installed binary):
|
|
@@ -123,6 +145,15 @@ pnpm dsh plugin --profile web exec modlens doctor
|
|
|
123
145
|
|
|
124
146
|
Configure the vision provider in the DSH settings page or in `C:\Users\<your-user>\.modlens\config.json`. Then create a conversation, upload an image, and ask the model to transcribe or explain it. Text-only models appear with a `(modlens vision)` entry when a compatible upstream route is available.
|
|
125
147
|
|
|
148
|
+
For ordinary web questions, ask for current sources or use the native `web_search`
|
|
149
|
+
tool; the bundle routes it through ModSearch. For a JavaScript, login, Cloudflare,
|
|
150
|
+
Turnstile, or other anti-bot page, the router provides the Ego Browser sequence:
|
|
151
|
+
`ego_space_open` -> `ego_navigate` -> `ego_page_info`/`ego_captcha` ->
|
|
152
|
+
`ego_snapshot`/`ego_screenshot`. When `humanCheck=true`, the current work package
|
|
153
|
+
pauses and the user completes the check in the Agent Browser observation window.
|
|
154
|
+
The router never fabricates a successful verification. Use `/router web` to inspect
|
|
155
|
+
the bundled versions and whether the Host services are detected.
|
|
156
|
+
|
|
126
157
|
### Update
|
|
127
158
|
|
|
128
159
|
To install the newest version visible on npm:
|
|
@@ -133,10 +164,10 @@ pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersi
|
|
|
133
164
|
```
|
|
134
165
|
|
|
135
166
|
For a reproducible deployment, replace `$routerVersion` with a concrete version
|
|
136
|
-
that you have verified with `npm view` (for example `0.4.
|
|
167
|
+
that you have verified with `npm view` (for example `0.4.14`):
|
|
137
168
|
|
|
138
169
|
```powershell
|
|
139
|
-
pnpm dsh plugin --profile web add @ljwei-stak/model-router-galgame@0.4.
|
|
170
|
+
pnpm dsh plugin --profile web add @ljwei-stak/model-router-galgame@0.4.14
|
|
140
171
|
```
|
|
141
172
|
|
|
142
173
|
You can also ask pnpm to update an already-installed package within its declared
|
|
@@ -159,10 +190,11 @@ cd F:\DeepSeek_harness\DSH-Desktop
|
|
|
159
190
|
pnpm dsh plugin --profile web remove @liustack/modlens
|
|
160
191
|
$routerVersion = npm view @ljwei-stak/model-router-galgame version --registry=https://registry.npmjs.org/
|
|
161
192
|
pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
|
|
162
|
-
pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens"
|
|
193
|
+
pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens|dsh-approval-gate|modsearch|ego-browser"
|
|
163
194
|
```
|
|
164
195
|
|
|
165
|
-
The dump should show one
|
|
196
|
+
The dump should show one row for each of `modlens`, `dsh-approval-gate`, `modsearch`,
|
|
197
|
+
`ego-browser`, and `model-router-galgame`. If the
|
|
166
198
|
error is `EADDRINUSE` on port 3080, or `task-board ledger is already owned by
|
|
167
199
|
process ...`, an old DSH process is still running; close that process before
|
|
168
200
|
starting another instance, or choose another port:
|
|
@@ -199,35 +231,331 @@ If no model is available, native model selection remains intact and the conversa
|
|
|
199
231
|
- `/router mode collective`
|
|
200
232
|
- `/router mode single`
|
|
201
233
|
- `/router plan`
|
|
234
|
+
- `/router safety`: show the approval adapter version, current stage, task count, active route, and risk candidate.
|
|
235
|
+
- `/router web`: show ModSearch/Ego Browser versions, service detection, and the current web strategy.
|
|
202
236
|
|
|
203
237
|
`collective` is the default. The plugin exposes task labels, scores, assignments, costs, and fallback records; it never exposes private model chain-of-thought.
|
|
204
238
|
|
|
239
|
+
### Approval and multi-task safety
|
|
240
|
+
|
|
241
|
+
The bundle loads `dsh-approval-gate` together with the router. To enable its learning-based auto-approval mode, select the target plugin's `auto-approve` permission preset in the profile; the default `ask` policy continues to use the native Harness approval UI. The router does not decide approval outcomes. It only adds safety context before `approval/request` enters the waterfall:
|
|
242
|
+
|
|
243
|
+
- Complex collective tasks with at least three work packages carry `risk_candidate=bulk`, and each stage is audited separately.
|
|
244
|
+
- Deletion, credential, remote, system, and bulk risks remain human-only in `dsh-approval-gate`.
|
|
245
|
+
- Flash timeout, errors, unparseable output, or failed similarity checks fail safe to a human review.
|
|
246
|
+
- Single-session mode keeps the native model and approval behavior.
|
|
247
|
+
|
|
248
|
+
Audit, learning, and snapshots use the target project's directory: `%DSH_HOME%\auto-approve\` (or `%USERPROFILE%\.dsh\auto-approve\` when `DSH_HOME` is unset), including `events.jsonl`, `audit.log`, `allowlist.json`, `learning.json`, and `snapshots\`. Run `/router safety` to inspect the bridge state.
|
|
249
|
+
|
|
250
|
+
### Web and anti-bot pages
|
|
251
|
+
|
|
252
|
+
ModSearch supplies ordinary web search and page reading, including source URLs and
|
|
253
|
+
fetch warnings. Pages that require login state, JavaScript rendering, or a visible
|
|
254
|
+
human check use Ego Browser. The model should open a browser space, navigate to the
|
|
255
|
+
URL, inspect `ego_page_info` or `ego_captcha`, and then collect evidence with
|
|
256
|
+
`ego_snapshot`, `ego_read_element`, `ego_screenshot`, or `ego_http(browser)`. Use
|
|
257
|
+
`ego_click`, `ego_fill`, and `ego_wait*` only when interaction is required. A
|
|
258
|
+
`humanCheck=true` result pauses the current work package until the user completes
|
|
259
|
+
the check in the Agent Browser window. Captcha and Cloudflare checks are never
|
|
260
|
+
bypassed or represented as successful without user confirmation.
|
|
261
|
+
|
|
205
262
|
## Mathematical routing model
|
|
206
263
|
|
|
207
|
-
|
|
264
|
+
This section describes the implementation in the form of the research framework in
|
|
265
|
+
`RESEARCH_PAPER_FRAMEWORK.md`. The production router and the offline experiment
|
|
266
|
+
plugin share the same objective, quality floors, cost model, and fallback semantics.
|
|
267
|
+
The production implementation adds Pareto pruning and bounded global search so that
|
|
268
|
+
the result is not merely a sequence of unrelated local choices.
|
|
269
|
+
|
|
270
|
+
### 1. Problem definition
|
|
271
|
+
|
|
272
|
+
For a request `x`, the router constructs:
|
|
273
|
+
|
|
274
|
+
```text
|
|
275
|
+
t task type: general, code, math, research, writing, summarization, vision
|
|
276
|
+
c complexity band: simple, balanced, or complex
|
|
277
|
+
I ordered work-package set
|
|
278
|
+
M discovered provider/model routes
|
|
279
|
+
F(i) quality floor for work package i
|
|
280
|
+
B optional per-request budget in USD
|
|
281
|
+
```
|
|
282
|
+
|
|
283
|
+
The assignment is $\pi: I \to M$. The primary objective is to maximize multi-objective
|
|
284
|
+
utility while satisfying quality constraints. When a budget is configured, it is a
|
|
285
|
+
hard secondary constraint:
|
|
286
|
+
|
|
287
|
+
$$
|
|
288
|
+
\begin{aligned}
|
|
289
|
+
\text{maximize}\quad & \sum_{i \in I} U(i,\pi(i)) \\
|
|
290
|
+
\text{subject to}\quad & Q(i,\pi(i)) \ge F(i), && \forall i \in I \\
|
|
291
|
+
& \sum_{i \in I} \mathrm{Cost}(i,\pi(i)) \le B
|
|
292
|
+
\end{aligned}
|
|
293
|
+
$$
|
|
294
|
+
|
|
295
|
+
If no model satisfies a particular floor, the router chooses the highest-quality
|
|
296
|
+
available fallback and records `constraintRelaxed: true`; it never silently claims
|
|
297
|
+
that an unavailable constraint was satisfied.
|
|
298
|
+
|
|
299
|
+
### 2. Request analysis and work-package construction
|
|
300
|
+
|
|
301
|
+
Task classification is signal based and deterministic. The classifier counts explicit
|
|
302
|
+
markers for code, mathematics, research, writing, summarization, and vision, then
|
|
303
|
+
uses the strongest signal as the primary type while retaining all detected types for
|
|
304
|
+
complex-task decomposition.
|
|
305
|
+
|
|
306
|
+
Complexity is a bounded score assembled from text length, list/requirement density,
|
|
307
|
+
domain markers, code/reasoning markers, and vision markers. The bands are:
|
|
208
308
|
|
|
209
|
-
|
|
309
|
+
$$
|
|
310
|
+
\begin{aligned}
|
|
311
|
+
\text{simple:}\quad & 0.00 \le \mathrm{complexity} < 0.34 \\
|
|
312
|
+
\text{balanced:}\quad & 0.34 \le \mathrm{complexity} < 0.66 \\
|
|
313
|
+
\text{complex:}\quad & 0.66 \le \mathrm{complexity} \le 1.00
|
|
314
|
+
\end{aligned}
|
|
315
|
+
$$
|
|
316
|
+
|
|
317
|
+
Simple and balanced requests use one execution package. A complex request is expanded
|
|
318
|
+
into a small DAG-like sequence:
|
|
210
319
|
|
|
211
320
|
```text
|
|
212
|
-
|
|
213
|
-
+ ws(c) S(i,m) - wr(c) R(m)
|
|
214
|
-
- lambda * 1[m is already used]
|
|
215
|
-
- kappa * max(0, F(i) - Q(i,m))
|
|
321
|
+
analysis -> domain execution packages -> optional verification -> synthesis
|
|
216
322
|
```
|
|
217
323
|
|
|
218
|
-
|
|
324
|
+
Every package has an id, type, purpose, criticality, quality floor, and `dependsOn`
|
|
325
|
+
list. The default floors are `0.75`, `0.78`, and `0.82` for simple, balanced, and
|
|
326
|
+
complex work. A complex synthesis package has a minimum floor of `0.84`; critical
|
|
327
|
+
non-synthesis packages receive a small additional floor based on criticality.
|
|
328
|
+
|
|
329
|
+
### 3. Model quality, specialty, cost, and risk
|
|
330
|
+
|
|
331
|
+
For route `m` and task type `t`, quality is resolved in this order:
|
|
332
|
+
|
|
333
|
+
$$
|
|
334
|
+
Q(m,t)=
|
|
335
|
+
\begin{cases}
|
|
336
|
+
\text{LiveBench category score}, & \text{when available};\\
|
|
337
|
+
\text{LiveBench overall score}, & \text{otherwise};\\
|
|
338
|
+
\text{catalog quality baseline}, & \text{otherwise}
|
|
339
|
+
\end{cases}
|
|
340
|
+
$$
|
|
341
|
+
|
|
342
|
+
Specialty `S(m,t)` is `1.0` for an explicit catalog specialty, `0.58` for a general
|
|
343
|
+
task, and a deterministic partial match for related domains. Risk `R(m)` and latency
|
|
344
|
+
`L(m)` are normalized catalog values; user pricing overrides only affect cost.
|
|
345
|
+
|
|
346
|
+
With input/output prices in USD per one million tokens, cache-aware cost is:
|
|
347
|
+
|
|
348
|
+
$$
|
|
349
|
+
\mathrm{Cost}(i,m)=
|
|
350
|
+
\frac{(n_{in}-n_{cache\_read}-n_{cache\_write})p_{in}
|
|
351
|
+
+n_{cache\_read}p_{cache\_read}
|
|
352
|
+
+n_{cache\_write}p_{cache\_write}
|
|
353
|
+
+n_{out}p_{out}}{10^6}
|
|
354
|
+
$$
|
|
355
|
+
|
|
356
|
+
The cache ratios are clamped to `[0,1]` and write ratio cannot overlap the read ratio.
|
|
357
|
+
If no cache ratio is configured, ordinary input pricing is used.
|
|
358
|
+
|
|
359
|
+
### 4. Multi-objective utility
|
|
360
|
+
|
|
361
|
+
The implementation uses the normalized cost score
|
|
362
|
+
$C_{\mathrm{norm}}=1-p_{\mathrm{effective}}/p_{\max}$, so a lower price receives a
|
|
363
|
+
larger utility contribution. For a
|
|
364
|
+
work package `i` and candidate `m`:
|
|
365
|
+
|
|
366
|
+
$$
|
|
367
|
+
\begin{aligned}
|
|
368
|
+
U(i,m)={}&w_q(c)Q(i,m)+w_c(c)C_{\mathrm{norm}}(m)+w_l(c)(1-L(m))\\
|
|
369
|
+
&+w_s(c)S(i,m)-w_r(c)R(m)\\
|
|
370
|
+
&-\lambda\,\mathbb{1}[m\text{ already used}]
|
|
371
|
+
-\kappa\max(0,F(i)-Q(i,m))\\
|
|
372
|
+
&+\mathrm{synthesis\_bonus}(i,m)
|
|
373
|
+
\end{aligned}
|
|
374
|
+
$$
|
|
375
|
+
|
|
376
|
+
The default weight vectors are:
|
|
377
|
+
|
|
378
|
+
| Complexity | Quality | Cost | Latency | Specialty | Risk |
|
|
379
|
+
|---|---:|---:|---:|---:|---:|
|
|
380
|
+
| simple | 0.30 | 0.50 | 0.14 | 0.04 | 0.02 |
|
|
381
|
+
| balanced | 0.45 | 0.30 | 0.10 | 0.10 | 0.05 |
|
|
382
|
+
| complex | 0.55 | 0.16 | 0.06 | 0.16 | 0.07 |
|
|
383
|
+
|
|
384
|
+
For synthesis, the quality-oriented vector is `0.70/0.10/0.04/0.10/0.06`, and
|
|
385
|
+
DeepSeek V4 Pro receives a small deterministic preference bonus when present. The
|
|
386
|
+
bonus is soft: if that route is unavailable, the normal feasible ranking remains in
|
|
387
|
+
force. Reusing a route costs `0.08` utility; changing routes across a dependency
|
|
388
|
+
boundary costs `0.015` in the global assignment search.
|
|
389
|
+
|
|
390
|
+
### 5. Production algorithm: Pareto-pruned constrained beam assignment
|
|
391
|
+
|
|
392
|
+
The current Host router is a bounded global solver with five stages.
|
|
393
|
+
|
|
394
|
+
#### 5.1 Candidate discovery and quality filtering
|
|
395
|
+
|
|
396
|
+
For each work package, routes below its quality floor are removed when at least one
|
|
397
|
+
qualified route exists. If none exists, at most the three highest-quality routes are
|
|
398
|
+
retained and the package is marked as relaxed. This makes constraint failure visible
|
|
399
|
+
and bounds the work on large model catalogs.
|
|
400
|
+
|
|
401
|
+
#### 5.2 Pareto pruning
|
|
402
|
+
|
|
403
|
+
Candidate `a` dominates candidate `b` for the same package when it is no worse in all
|
|
404
|
+
five dimensions and strictly better in at least one:
|
|
405
|
+
|
|
406
|
+
$$
|
|
407
|
+
Q(a)\ge Q(b),\quad \mathrm{Cost}(a)\le\mathrm{Cost}(b),\quad L(a)\le L(b),\quad
|
|
408
|
+
S(a)\ge S(b),\quad R(a)\le R(b)
|
|
409
|
+
$$
|
|
410
|
+
|
|
411
|
+
Dominated candidates cannot improve quality, cost, latency, specialty, or risk. The
|
|
412
|
+
router keeps the Pareto frontier plus three anchors: the cheapest candidate, the
|
|
413
|
+
highest-utility candidate, and the highest-quality candidate. The per-package pool is
|
|
414
|
+
limited to 12 routes.
|
|
415
|
+
|
|
416
|
+
#### 5.3 Dependency-aware beam search
|
|
417
|
+
|
|
418
|
+
Each beam state stores the partial assignment, route selected for every completed
|
|
419
|
+
package, total cost, utility, number of dependency handoffs, and accumulated quality
|
|
420
|
+
shortfall. States are expanded in package order. A child receives the candidate
|
|
421
|
+
utility minus `0.015` for every dependency edge that crosses to a different route.
|
|
422
|
+
The beam width is 256. Ties are resolved by quality shortfall, utility, cost,
|
|
423
|
+
handoffs, and finally lexical provider/model order, making repeated plans stable.
|
|
424
|
+
|
|
425
|
+
The search first minimizes constraint violations, then quality shortfall, and then
|
|
426
|
+
maximizes utility. With a budget, suffix minimum-cost bounds prune partial states that
|
|
427
|
+
cannot possibly fit the remaining budget.
|
|
428
|
+
|
|
429
|
+
#### 5.4 Budget strategy
|
|
430
|
+
|
|
431
|
+
The router evaluates three plans:
|
|
432
|
+
|
|
433
|
+
1. an unconstrained utility plan;
|
|
434
|
+
2. a utility plan that must fit `B`;
|
|
435
|
+
3. when (2) is infeasible, a minimum-cost plan that still preserves every available
|
|
436
|
+
quality floor.
|
|
219
437
|
|
|
220
|
-
|
|
438
|
+
If no floor-preserving plan exists, the least-cost best-quality fallback is returned,
|
|
439
|
+
`budgetExceeded` and/or `constraintRelaxed` are exposed in the audit record, and the
|
|
440
|
+
UI explains why the target could not be met. This is a global replacement strategy,
|
|
441
|
+
not a greedy “replace the last stage” rule.
|
|
221
442
|
|
|
222
|
-
|
|
443
|
+
#### 5.5 Production pseudocode
|
|
223
444
|
|
|
224
445
|
```text
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
446
|
+
BuildPlan(x, M, B):
|
|
447
|
+
(t, c, I) <- AnalyzeRequest(x)
|
|
448
|
+
for i in I:
|
|
449
|
+
P_i <- FeasibleCandidates(i, M)
|
|
450
|
+
P_i <- ParetoPrune(P_i) + {cheapest, best-utility, best-quality}
|
|
451
|
+
plan <- BeamAssign(I, P, B = infinity)
|
|
452
|
+
if B > 0:
|
|
453
|
+
budgetPlan <- BeamAssign(I, P, B)
|
|
454
|
+
plan <- budgetPlan if feasible
|
|
455
|
+
else BeamAssign(I, P, minimize total cost)
|
|
456
|
+
return auditable assignments, costs, floors, handoffs, and fallback flags
|
|
228
457
|
```
|
|
229
458
|
|
|
230
|
-
|
|
459
|
+
### 6. Experiment algorithms
|
|
460
|
+
|
|
461
|
+
The `experiment-plugin` contains standalone implementations used by the six paper
|
|
462
|
+
experiments. They are intentionally deterministic and use the same model schema as
|
|
463
|
+
the Host router.
|
|
464
|
+
|
|
465
|
+
**QCG-Router (quality-constrained greedy / Pareto variant)**
|
|
466
|
+
|
|
467
|
+
QCG evaluates every model in $O(\lvert M\rvert)$, predicts quality from the baseline score plus a
|
|
468
|
+
specialty bonus, removes candidates below `F(i)`, computes the five-objective utility,
|
|
469
|
+
and selects the first Pareto/utility candidate. If the feasible set is empty, it
|
|
470
|
+
returns the highest-quality fallback with `constraintRelaxed: true`.
|
|
471
|
+
|
|
472
|
+
**AMO-Router (adaptive multi-objective routing)**
|
|
473
|
+
|
|
474
|
+
AMO starts from the paper's complexity-specific weights. After observing actual cost
|
|
475
|
+
and quality it computes:
|
|
476
|
+
|
|
477
|
+
$$
|
|
478
|
+
e_{cost}=\mathrm{clamp}\!\left(\frac{\mathrm{actual\_cost}-\mathrm{target\_cost}}
|
|
479
|
+
{\max(\mathrm{target\_cost},\varepsilon)}\right),\qquad
|
|
480
|
+
v_q=\max(0,\mathrm{quality\_floor}-\mathrm{actual\_quality})
|
|
481
|
+
$$
|
|
482
|
+
|
|
483
|
+
The feedback is exponentially smoothed (`0.10`). Positive cost error increases cost
|
|
484
|
+
pressure; a quality violation increases quality and specialty pressure. Weights are
|
|
485
|
+
projected back to the positive simplex after every update, so they remain finite,
|
|
486
|
+
positive, and sum to one. This fixes the sign ambiguity that could previously reduce
|
|
487
|
+
cost pressure when observed cost was too high.
|
|
488
|
+
|
|
489
|
+
**DAG-Assign (dependency-aware task allocation)**
|
|
490
|
+
|
|
491
|
+
DAG-Assign uses Kahn topological sorting, rejects unknown edge endpoints and cycles,
|
|
492
|
+
and computes criticality as the number of unique descendants plus `100` for a
|
|
493
|
+
synthesis node. For every node it keeps the QCG Pareto candidates, then runs a bounded
|
|
494
|
+
beam assignment with dependency handoff penalties, a synthesis quality bonus, and a
|
|
495
|
+
criticality bonus. Budget pruning uses suffix lower bounds; if the budget is
|
|
496
|
+
impossible, the result explicitly reports `budgetFeasible: false` instead of silently
|
|
497
|
+
assigning a below-floor model.
|
|
498
|
+
|
|
499
|
+
### 7. Complexity and correctness properties
|
|
500
|
+
|
|
501
|
+
Let $N=\lvert M\rvert$, $K\le 12$ be the retained candidate pool,
|
|
502
|
+
$P=\lvert I\rvert$, and $W=256$ be
|
|
503
|
+
the beam width. The current implementation has the following bounded worst-case
|
|
504
|
+
costs:
|
|
505
|
+
|
|
506
|
+
| Component | Time complexity | Space complexity |
|
|
507
|
+
|---|---:|---:|
|
|
508
|
+
| Candidate scoring | $O(PN)$ | $O(PN)$ |
|
|
509
|
+
| Pairwise Pareto pruning | $O(PN^2)$ | $O(PN)$ |
|
|
510
|
+
| Beam assignment | $O(PWK)$ | $O(WK+P)$ |
|
|
511
|
+
| DAG topological sort | $O(\lvert V\rvert+\lvert E\rvert)$ | $O(\lvert V\rvert+\lvert E\rvert)$ |
|
|
512
|
+
|
|
513
|
+
The constants are small for desktop catalogs, and all loops are bounded by the
|
|
514
|
+
discovered routes, 12 candidates per package, and beam width 256.
|
|
515
|
+
|
|
516
|
+
The following invariants are enforced and exposed in the result:
|
|
517
|
+
|
|
518
|
+
1. **Quality guarantee**: if a qualified candidate exists for a package, every normal
|
|
519
|
+
assignment considered by the solver satisfies $Q\ge F$.
|
|
520
|
+
2. **Budget guarantee**: a plan marked `budgetFeasible: true` has estimated total cost
|
|
521
|
+
no greater than `B`, subject to the configured token and price estimates.
|
|
522
|
+
3. **Dependency guarantee**: every collaboration stage is emitted in topological
|
|
523
|
+
order, and handoff count is recorded.
|
|
524
|
+
4. **Determinism**: equal scores use stable cost and route-id tie breakers; repeated
|
|
525
|
+
input/catalog/settings produce the same plan.
|
|
526
|
+
5. **Graceful degradation**: no models, failed providers, stale LiveBench data, and
|
|
527
|
+
unsatisfied floors are represented as explicit fallback metadata rather than
|
|
528
|
+
blocking the native Harness request path.
|
|
529
|
+
|
|
530
|
+
The beam solver is deliberately bounded. It provides an auditable, deterministic
|
|
531
|
+
near-optimal heuristic for interactive desktop routing, not a formal global-optimum
|
|
532
|
+
guarantee for arbitrary DAGs. A larger beam improves search coverage at the cost of
|
|
533
|
+
latency; Pareto pruning and suffix lower bounds keep the default `W = 256` practical.
|
|
534
|
+
|
|
535
|
+
### 8. Cost and audit outputs
|
|
536
|
+
|
|
537
|
+
For every plan the router reports:
|
|
538
|
+
|
|
539
|
+
$$
|
|
540
|
+
\begin{aligned}
|
|
541
|
+
\mathrm{TotalCost}&=\sum_{i\in I}\mathrm{Cost}(i,\mathrm{assign}(i)),\\
|
|
542
|
+
\mathrm{BaselineCost}&=\text{cost of the strongest available model per package},\\
|
|
543
|
+
\mathrm{EstimatedSaving}&=\max\!\left(0,1-\frac{\mathrm{TotalCost}}{\mathrm{BaselineCost}}\right)
|
|
544
|
+
\end{aligned}
|
|
545
|
+
$$
|
|
546
|
+
|
|
547
|
+
The plan also contains per-stage token estimates, cache read/write tokens, predicted
|
|
548
|
+
quality, quality floor, provider/model, Pareto-pruned count, beam width, handoff count,
|
|
549
|
+
budget feasibility, and whether constraints were relaxed. `/router plan` and the GAL
|
|
550
|
+
analysis panel display these audit fields; neither exposes private model chain of
|
|
551
|
+
thought.
|
|
552
|
+
|
|
553
|
+
The algorithm is wired into the Host request path: `index.mjs` calls `buildPlan` in
|
|
554
|
+
collective mode and executes the planned stages. Single-session mode preserves the
|
|
555
|
+
explicitly selected model instead of applying the collective override. The regression
|
|
556
|
+
suite covers complexity, mixed-domain decomposition, LiveBench and price overrides,
|
|
557
|
+
budget behavior, Pareto pruning, AMO feedback direction, DAG ordering, multi-stage
|
|
558
|
+
execution, and final synthesis.
|
|
231
559
|
|
|
232
560
|
## OpenCode Zen settings
|
|
233
561
|
|
package/README.zh.md
CHANGED
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
- **GAL 视窗**:新会话自动形成存档;历史记录保留实际 provider/model,名牌、颜色和立绘随当前模型变化。ERNIE、文心一言和百度 provider/model 标识统一显示 `ERNIE娘` 与 `ernie1.png`。路由分析显示的是可审计摘要,不是模型私有思维链。
|
|
16
16
|
- **Markdown/KaTeX**:复用 Harness 的 `MarkdownText`,支持标题、列表、表格、引用、代码、链接和数学公式;宽表格、代码块和公式在对话框内滚动,玩家输入保持纯文本。
|
|
17
17
|
- **附件与多模态**:图片使用原生多模态管线,Markdown/TXT/JSON/代码文件提取为文本;PDF/DOCX 等二进制文件保留解析状态,不会静默伪造内容。
|
|
18
|
+
- **多任务安全审批适配**:内置 `dsh-approval-gate@0.5.0` 适配。复杂集体任务的每次沙箱升级都会附带工作包、阶段、路由和任务数量上下文;批量任务标记为 `bulk` 风险候选,由审批插件执行 Flash 判定、硬风险人工确认、学习白名单、审计和快照。单独会话不会被路由器强制切换。
|
|
19
|
+
- **联网与反爬窗口**:内置 `@liustack/modsearch@5.10.1` 接管原生 `web_search`,并提供 `read_page`/`x_search`;内置 `dsh-ego-browser@0.8.0` 提供真实 Chrome 窗口、语义树、截图、点击、登录态和验证码检测。ModSearch 抓取失败或遇到动态/反爬页面时,路由指导模型切换到 Ego Browser;检测到人机验证会暂停并让用户在观察窗完成。
|
|
18
20
|
- **OpenCode Zen**:官方站点覆盖会自动恢复模型目录所需的 `/zen`、`/zen/v1` 端点;自定义网关不受影响。
|
|
19
21
|
- **更新与桌面端**:插件设置页提供 GitHub Release 检查和一键更新;客户端过期时更新完整客户端及内置插件,否则只更新插件。网页端无法写入本机文件时会打开 Releases 页面。
|
|
20
22
|
|
|
@@ -29,7 +31,7 @@
|
|
|
29
31
|
|
|
30
32
|
### 推荐方式:安装已发布的 npm 包
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
这个公开包会从 npm registry 安装官方 `@liustack/modlens@3.25.4`、`dsh-approval-gate@0.5.0`、`@liustack/modsearch@5.10.1` 和 `dsh-ego-browser@0.8.0`,同时带上 Ego Browser 实际运行所需的 `@deepseek-ai/dsh-tools@0.1.0-rc.8`、`schemastery@3.18.0`。无需克隆 Ego Browser GitHub 仓库,也无需单独安装这些依赖。
|
|
33
35
|
|
|
34
36
|
1. 在 PowerShell 中进入 DSH Desktop 目录:
|
|
35
37
|
|
|
@@ -74,7 +76,7 @@ pnpm dsh plugin --profile web add --registry=https://registry.npmjs.org "@ljwei-
|
|
|
74
76
|
4. 检查路由器和它的 ModLens 依赖是否已加入 profile:
|
|
75
77
|
|
|
76
78
|
```powershell
|
|
77
|
-
pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens"
|
|
79
|
+
pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens|dsh-approval-gate|modsearch|ego-browser"
|
|
78
80
|
```
|
|
79
81
|
|
|
80
82
|
输出中应包含:
|
|
@@ -82,6 +84,9 @@ pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modle
|
|
|
82
84
|
```text
|
|
83
85
|
@ljwei-stak/model-router-galgame
|
|
84
86
|
@liustack/modlens
|
|
87
|
+
dsh-approval-gate
|
|
88
|
+
@liustack/modsearch
|
|
89
|
+
dsh-ego-browser
|
|
85
90
|
```
|
|
86
91
|
|
|
87
92
|
安装本插件后不要再单独添加 `@liustack/modlens`。本插件已经包含官方 ModLens
|
|
@@ -93,6 +98,23 @@ pnpm dsh plugin --profile web remove @liustack/modlens
|
|
|
93
98
|
pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
|
|
94
99
|
```
|
|
95
100
|
|
|
101
|
+
`modsearch` 和 `dsh-ego-browser` 也不要在同一 profile 中重复安装。重复条目会造成
|
|
102
|
+
搜索 provider 或浏览器工具重复注册:
|
|
103
|
+
|
|
104
|
+
```powershell
|
|
105
|
+
pnpm dsh plugin --profile web remove @liustack/modsearch
|
|
106
|
+
pnpm dsh plugin --profile web remove dsh-ego-browser
|
|
107
|
+
pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
同理,不要在同一个 profile 中再次单独添加 `dsh-approval-gate`。如果以前单独安装过,
|
|
111
|
+
先删除旧条目再安装本插件,避免重复 loader:
|
|
112
|
+
|
|
113
|
+
```powershell
|
|
114
|
+
pnpm dsh plugin --profile web remove dsh-approval-gate
|
|
115
|
+
pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
|
|
116
|
+
```
|
|
117
|
+
|
|
96
118
|
5. 关闭已经运行的 DSH,再启动对应 profile:
|
|
97
119
|
|
|
98
120
|
```powershell
|
|
@@ -129,10 +151,10 @@ pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersi
|
|
|
129
151
|
```
|
|
130
152
|
|
|
131
153
|
如果需要可复现部署,请把 `$routerVersion` 替换为通过 `npm view` 确认过的具体版本号
|
|
132
|
-
(例如 `0.4.
|
|
154
|
+
(例如 `0.4.13`):
|
|
133
155
|
|
|
134
156
|
```powershell
|
|
135
|
-
pnpm dsh plugin --profile web add @ljwei-stak/model-router-galgame@0.4.
|
|
157
|
+
pnpm dsh plugin --profile web add @ljwei-stak/model-router-galgame@0.4.13
|
|
136
158
|
```
|
|
137
159
|
|
|
138
160
|
已经安装过插件时,也可以让 pnpm 在当前版本范围内更新:
|
|
@@ -154,10 +176,10 @@ cd F:\DeepSeek_harness\DSH-Desktop
|
|
|
154
176
|
pnpm dsh plugin --profile web remove @liustack/modlens
|
|
155
177
|
$routerVersion = npm view @ljwei-stak/model-router-galgame version --registry=https://registry.npmjs.org/
|
|
156
178
|
pnpm dsh plugin --profile web add "@ljwei-stak/model-router-galgame@$routerVersion"
|
|
157
|
-
pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens"
|
|
179
|
+
pnpm dsh --profile web --dump-config | Select-String "model-router-galgame|modlens|dsh-approval-gate"
|
|
158
180
|
```
|
|
159
181
|
|
|
160
|
-
输出应只显示一条 `modlens` 行和一条 `model-router-galgame` 行。如果报
|
|
182
|
+
输出应只显示一条 `modlens` 行、一条 `dsh-approval-gate` 行和一条 `model-router-galgame` 行。如果报
|
|
161
183
|
`EADDRINUSE` 且端口为 3080,或报 `task-board ledger is already owned by process ...`,
|
|
162
184
|
说明旧的 DSH 进程仍在运行;先关闭旧进程,或换一个端口启动:
|
|
163
185
|
|
|
@@ -193,35 +215,303 @@ pnpm dsh plugin --profile desktop remove @ljwei-stak/model-router-galgame
|
|
|
193
215
|
- `/router mode collective`
|
|
194
216
|
- `/router mode single`
|
|
195
217
|
- `/router plan`
|
|
218
|
+
- `/router safety`:查看审批适配版本、当前阶段、任务数量、活动路由和风险候选。
|
|
219
|
+
- `/router web`:查看 ModSearch/Ego Browser 版本、服务探测状态和当前联网策略。
|
|
196
220
|
|
|
197
221
|
默认模式为 `collective`。系统只公开任务分类、评分、分配、费用和回退记录,不输出任何模型私有思维链。
|
|
198
222
|
|
|
223
|
+
### 审批与多任务安全策略
|
|
224
|
+
|
|
225
|
+
安装后,profile 的 bundle 会同时加载 `dsh-approval-gate`。要启用其自动审批学习模式,
|
|
226
|
+
请在 profile 的权限预设中选择目标插件提供的 `auto-approve`;默认 `ask` 策略仍然由
|
|
227
|
+
Harness 原生审批界面处理。路由器不会改变审批结论,只会在 `approval/request` 进入瀑布
|
|
228
|
+
前补充安全上下文:
|
|
229
|
+
|
|
230
|
+
- 复杂集体任务(至少 3 个工作包)标记 `risk_candidate=bulk`,每个阶段单独审计。
|
|
231
|
+
- 删除、凭据、远程、系统和批量风险由 `dsh-approval-gate` 永久要求人工确认。
|
|
232
|
+
- Flash 超时、异常、输出无法解析或同类验证失败时,按 fail-safe 转人工。
|
|
233
|
+
- 单独会话保留原生模型和审批行为,不会被集体路由计划覆盖。
|
|
234
|
+
|
|
235
|
+
审计、学习和快照沿用目标项目的目录:`%DSH_HOME%\auto-approve\`(未设置
|
|
236
|
+
`DSH_HOME` 时为 `%USERPROFILE%\.dsh\auto-approve\`),包括 `events.jsonl`、
|
|
237
|
+
`audit.log`、`allowlist.json`、`learning.json` 和 `snapshots\`。如需确认桥接状态,
|
|
238
|
+
在会话中运行 `/router safety`。
|
|
239
|
+
|
|
240
|
+
### 联网和反爬页面
|
|
241
|
+
|
|
242
|
+
普通搜索和页面阅读由 ModSearch 处理,结果带有来源 URL、不确定性和抓取警告。需要
|
|
243
|
+
登录态、JavaScript 动态渲染、验证码或 Cloudflare/Turnstile 的页面由 Ego Browser
|
|
244
|
+
接管。模型应按以下顺序工作:`ego_space_open` → `ego_navigate` → `ego_page_info`/
|
|
245
|
+
`ego_captcha` → `ego_snapshot`/`ego_screenshot`;需要交互时使用 `ego_click`、
|
|
246
|
+
`ego_fill` 或 `ego_wait*`。检测到 `humanCheck=true` 后,当前工作包会暂停,用户在
|
|
247
|
+
“Agent 浏览器”观察窗完成验证,确认后再继续。系统不会尝试绕过验证码或伪造结果。
|
|
248
|
+
|
|
249
|
+
在 Web profile 中,Ego Browser 会注册 `/api/ego/*` 观察窗路由;若安装了
|
|
250
|
+
`dsh-better-sidebar`,观察窗显示为“Agent 浏览器”侧边栏 Tab,否则显示浮动观察窗。
|
|
251
|
+
|
|
199
252
|
## 数学路由模型
|
|
200
253
|
|
|
201
|
-
|
|
254
|
+
本节按照 `RESEARCH_PAPER_FRAMEWORK.md` 的论文框架,完整说明当前 `0.4.14`
|
|
255
|
+
实现。生产路由器与离线实验插件共用质量下限、费用模型、目标函数和回退语义;
|
|
256
|
+
生产实现进一步加入 Pareto 剪枝与有界全局搜索,因此不再是互相独立的逐阶段局部贪心。
|
|
257
|
+
|
|
258
|
+
### 1. 问题定义
|
|
202
259
|
|
|
203
|
-
|
|
260
|
+
给定用户请求 `x`,系统构造:
|
|
204
261
|
|
|
205
262
|
```text
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
263
|
+
t 任务类型:general、code、math、research、writing、summarization、vision
|
|
264
|
+
c 复杂度档位:simple、balanced、complex
|
|
265
|
+
I 有序工作包集合
|
|
266
|
+
M 已发现的 provider/model 路由集合
|
|
267
|
+
F(i) 工作包 i 的质量下限
|
|
268
|
+
B 可选的单请求 USD 预算
|
|
210
269
|
```
|
|
211
270
|
|
|
212
|
-
|
|
271
|
+
分配函数为 $\pi: I \to M$。首要目标是在满足质量约束的前提下最大化多目标效用;
|
|
272
|
+
设置预算时,预算是第二层硬约束:
|
|
273
|
+
|
|
274
|
+
$$
|
|
275
|
+
\begin{aligned}
|
|
276
|
+
\text{最大化}\quad & \sum_{i \in I} U(i,\pi(i)) \\
|
|
277
|
+
\text{约束}\quad & Q(i,\pi(i)) \ge F(i), && \forall i \in I \\
|
|
278
|
+
& \sum_{i \in I} \mathrm{Cost}(i,\pi(i)) \le B
|
|
279
|
+
\end{aligned}
|
|
280
|
+
$$
|
|
281
|
+
|
|
282
|
+
如果某个工作包没有任何模型达到质量下限,系统会选择可用候选中质量最高的回退模型,
|
|
283
|
+
并写入 `constraintRelaxed: true`;不会把未满足的约束伪装成已满足。
|
|
213
284
|
|
|
214
|
-
|
|
285
|
+
### 2. 请求分析与工作包构造
|
|
215
286
|
|
|
216
|
-
|
|
287
|
+
任务分类是确定性的信号计数过程。系统分别统计代码、数学、研究、写作、摘要和视觉
|
|
288
|
+
关键词,以信号最多的类型作为主类型,同时保留所有检测到的类型用于复杂任务拆分。
|
|
289
|
+
|
|
290
|
+
复杂度分数由文本长度、条目/要求密度、领域关键词、代码/推理标记和视觉标记组成,
|
|
291
|
+
并限制在 `[0,1]`:
|
|
292
|
+
|
|
293
|
+
$$
|
|
294
|
+
\begin{aligned}
|
|
295
|
+
\text{simple:}\quad & 0.00 \le \mathrm{complexity} < 0.34 \\
|
|
296
|
+
\text{balanced:}\quad & 0.34 \le \mathrm{complexity} < 0.66 \\
|
|
297
|
+
\text{complex:}\quad & 0.66 \le \mathrm{complexity} \le 1.00
|
|
298
|
+
\end{aligned}
|
|
299
|
+
$$
|
|
300
|
+
|
|
301
|
+
简单和均衡请求使用一个执行工作包。复杂请求展开为一个小型 DAG 序列:
|
|
217
302
|
|
|
218
303
|
```text
|
|
219
|
-
|
|
220
|
-
TotalCost = sum_i Cost(i, assign(i))
|
|
221
|
-
Saving = max(0, 1 - TotalCost / BaselineStrongCost)
|
|
304
|
+
analysis -> 领域执行工作包 -> 可选 verification -> synthesis
|
|
222
305
|
```
|
|
223
306
|
|
|
224
|
-
|
|
307
|
+
每个工作包都具有 id、类型、用途、关键度、质量下限和 `dependsOn` 依赖列表。默认
|
|
308
|
+
质量下限为:simple `0.75`、balanced `0.78`、complex `0.82`。复杂任务的 synthesis
|
|
309
|
+
工作包最低为 `0.84`;关键度较高的非 synthesis 工作包会在基础下限上获得小幅增量。
|
|
310
|
+
|
|
311
|
+
### 3. 模型质量、专长、费用与风险
|
|
312
|
+
|
|
313
|
+
对于模型路由 `m` 和任务类型 `t`,质量按以下顺序解析:
|
|
314
|
+
|
|
315
|
+
$$
|
|
316
|
+
Q(m,t)=
|
|
317
|
+
\begin{cases}
|
|
318
|
+
\text{LiveBench 分类分数}, & \text{可用时};\\
|
|
319
|
+
\text{LiveBench overall 分数}, & \text{否则};\\
|
|
320
|
+
\text{仓库内实验基线分数}, & \text{否则}
|
|
321
|
+
\end{cases}
|
|
322
|
+
$$
|
|
323
|
+
|
|
324
|
+
专长 `S(m,t)`:模型明确声明该专长时为 `1.0`;general 任务为 `0.58`;研究/写作等
|
|
325
|
+
相关方向使用确定性的部分匹配。风险 `R(m)` 与延迟 `L(m)` 使用目录归一化值;用户
|
|
326
|
+
价格覆盖只改变费用,不会伪造质量分数。
|
|
327
|
+
|
|
328
|
+
输入/输出价格单位为 USD/百万 token,支持 prompt cache 的费用公式为:
|
|
329
|
+
|
|
330
|
+
$$
|
|
331
|
+
\mathrm{Cost}(i,m)=
|
|
332
|
+
\frac{(n_{in}-n_{cache\_read}-n_{cache\_write})p_{in}
|
|
333
|
+
+n_{cache\_read}p_{cache\_read}
|
|
334
|
+
+n_{cache\_write}p_{cache\_write}
|
|
335
|
+
+n_{out}p_{out}}{10^6}
|
|
336
|
+
$$
|
|
337
|
+
|
|
338
|
+
缓存读写比例会被限制在 `[0,1]`,写入比例不会与读取比例重叠;没有配置缓存比例时,
|
|
339
|
+
全部输入按普通输入价格计费。
|
|
340
|
+
|
|
341
|
+
### 4. 多目标效用函数
|
|
342
|
+
|
|
343
|
+
代码使用归一化费用效用
|
|
344
|
+
$C_{\mathrm{norm}}=1-p_{\mathrm{effective}}/p_{\max}$,因此
|
|
345
|
+
实际价格越低,成本目标贡献越高。单个工作包 `i` 选择模型 `m` 的效用为:
|
|
346
|
+
|
|
347
|
+
$$
|
|
348
|
+
\begin{aligned}
|
|
349
|
+
U(i,m)={}&w_q(c)Q(i,m)+w_c(c)C_{\mathrm{norm}}(m)+w_l(c)(1-L(m))\\
|
|
350
|
+
&+w_s(c)S(i,m)-w_r(c)R(m)\\
|
|
351
|
+
&-\lambda\,\mathbb{1}[m\text{ 已经使用}]
|
|
352
|
+
-\kappa\max(0,F(i)-Q(i,m))\\
|
|
353
|
+
&+\mathrm{synthesis\_bonus}(i,m)
|
|
354
|
+
\end{aligned}
|
|
355
|
+
$$
|
|
356
|
+
|
|
357
|
+
默认权重为:
|
|
358
|
+
|
|
359
|
+
| 复杂度 | 质量 | 成本 | 延迟 | 专长 | 风险 |
|
|
360
|
+
|---|---:|---:|---:|---:|---:|
|
|
361
|
+
| simple | 0.30 | 0.50 | 0.14 | 0.04 | 0.02 |
|
|
362
|
+
| balanced | 0.45 | 0.30 | 0.10 | 0.10 | 0.05 |
|
|
363
|
+
| complex | 0.55 | 0.16 | 0.06 | 0.16 | 0.07 |
|
|
364
|
+
|
|
365
|
+
synthesis 使用质量优先的 `0.70/0.10/0.04/0.10/0.06` 权重;存在 DeepSeek V4 Pro
|
|
366
|
+
时只增加一个小的确定性偏好项,并非硬编码强制选择,不可用时仍按可行候选排序回退。
|
|
367
|
+
重复使用同一路由扣除 `0.08` 效用;依赖边跨越不同模型时,在全局分配中每条边扣除
|
|
368
|
+
`0.015`,用于抑制不必要的上下文交接。
|
|
369
|
+
|
|
370
|
+
### 5. 生产算法:Pareto 剪枝的质量约束 Beam Assignment
|
|
371
|
+
|
|
372
|
+
当前 Host 路由器由五个步骤组成。
|
|
373
|
+
|
|
374
|
+
#### 5.1 候选发现与质量过滤
|
|
375
|
+
|
|
376
|
+
对每个工作包,若至少存在一个达到质量下限的模型,就删除所有低于下限的候选;若一个
|
|
377
|
+
都不存在,则保留质量最高的至多三个候选,并标记该工作包需要放宽约束。这样既保证正常
|
|
378
|
+
情况下的质量硬约束,也让约束失败可见,并限制大模型目录下的计算量。
|
|
379
|
+
|
|
380
|
+
#### 5.2 Pareto 剪枝
|
|
381
|
+
|
|
382
|
+
对于同一个工作包,候选 `a` 支配候选 `b` 的条件是:五个维度全部不差,且至少一个维度
|
|
383
|
+
严格更好:
|
|
384
|
+
|
|
385
|
+
$$
|
|
386
|
+
Q(a)\ge Q(b),\quad \mathrm{Cost}(a)\le\mathrm{Cost}(b),\quad L(a)\le L(b),\quad
|
|
387
|
+
S(a)\ge S(b),\quad R(a)\le R(b)
|
|
388
|
+
$$
|
|
389
|
+
|
|
390
|
+
被支配的模型不可能同时改善质量、费用、延迟、专长或风险,因此可以安全删除。系统保留
|
|
391
|
+
Pareto 前沿,并额外保留三个锚点:最低费用、最高综合效用和最高质量候选;每个工作包
|
|
392
|
+
最终至多保留 12 条路由。
|
|
393
|
+
|
|
394
|
+
#### 5.3 依赖感知的 Beam Search
|
|
395
|
+
|
|
396
|
+
每个 Beam 状态保存:部分分配、已完成工作包的路由映射、累计费用、累计效用、依赖交接
|
|
397
|
+
次数和质量缺口。算法按工作包顺序扩展状态;如果当前模型与依赖工作包模型不同,就按
|
|
398
|
+
每条依赖边扣除 `0.015`。Beam 宽度为 256。并列状态按质量缺口、综合效用、费用、交接
|
|
399
|
+
次数和 provider/model 字典序稳定决胜,因此相同输入会得到相同方案。
|
|
400
|
+
|
|
401
|
+
搜索排序优先减少质量约束违规,再减少质量缺口,最后最大化效用。预算搜索使用“后缀最低
|
|
402
|
+
费用”下界,提前剪掉即使后续全部使用最便宜模型也无法满足预算的部分状态。
|
|
403
|
+
|
|
404
|
+
#### 5.4 预算策略
|
|
405
|
+
|
|
406
|
+
系统依次评估三种方案:
|
|
407
|
+
|
|
408
|
+
1. 不设预算的效用最优方案;
|
|
409
|
+
2. 必须满足 `B` 的效用方案;
|
|
410
|
+
3. 如果第 2 项不可行,则求仍保持所有可用质量下限的最低费用方案。
|
|
411
|
+
|
|
412
|
+
如果连保持质量下限的方案都不存在,就返回“质量最高的低成本回退”,并在审计结果中明确
|
|
413
|
+
写入 `budgetExceeded` 和/或 `constraintRelaxed`。这是全局组合替换,不是简单地只替换
|
|
414
|
+
最后一个阶段。
|
|
415
|
+
|
|
416
|
+
#### 5.5 生产伪代码
|
|
417
|
+
|
|
418
|
+
```text
|
|
419
|
+
BuildPlan(x, M, B):
|
|
420
|
+
(t, c, I) <- AnalyzeRequest(x)
|
|
421
|
+
for i in I:
|
|
422
|
+
P_i <- FeasibleCandidates(i, M)
|
|
423
|
+
P_i <- ParetoPrune(P_i) + {最低费用、最高效用、最高质量}
|
|
424
|
+
plan <- BeamAssign(I, P, B = infinity)
|
|
425
|
+
if B > 0:
|
|
426
|
+
budgetPlan <- BeamAssign(I, P, B)
|
|
427
|
+
plan <- budgetPlan if feasible
|
|
428
|
+
else BeamAssign(I, P, minimize total cost)
|
|
429
|
+
return assignments、费用、质量下限、交接次数和回退标记
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
### 6. 实验插件中的三种算法
|
|
433
|
+
|
|
434
|
+
`experiment-plugin` 提供论文六项实验使用的独立算法实现。它们使用与 Host 相同的模型
|
|
435
|
+
字段,并采用固定随机种子保证离线结果可复现。
|
|
436
|
+
|
|
437
|
+
**QCG-Router(质量约束贪心 / Pareto 变体)**
|
|
438
|
+
|
|
439
|
+
QCG 对每个模型进行质量、费用、延迟、专长和风险评估;质量预测为基线分数加专长奖励,
|
|
440
|
+
复杂任务有小幅复杂度扣减。算法先过滤低于 `F(i)` 的候选,再从 Pareto 前沿中选择效用
|
|
441
|
+
最高者。若没有可行候选,则返回质量最高的回退并设置 `constraintRelaxed: true`。
|
|
442
|
+
|
|
443
|
+
**AMO-Router(自适应多目标路由)**
|
|
444
|
+
|
|
445
|
+
AMO 从论文规定的三组复杂度权重开始。得到真实费用和质量后计算:
|
|
446
|
+
|
|
447
|
+
$$
|
|
448
|
+
e_{cost}=\mathrm{clamp}\!\left(\frac{\mathrm{actual\_cost}-\mathrm{target\_cost}}
|
|
449
|
+
{\max(\mathrm{target\_cost},\varepsilon)}\right),\qquad
|
|
450
|
+
v_q=\max(0,\mathrm{quality\_floor}-\mathrm{actual\_quality})
|
|
451
|
+
$$
|
|
452
|
+
|
|
453
|
+
反馈使用 `0.10` 的指数平滑。实际费用高于目标时提高成本目标权重;质量违反时提高质量
|
|
454
|
+
和专长权重。每次更新后投影回正权重单纯形,保证五个权重有限、为正且总和为 1。这样修正
|
|
455
|
+
了旧实现中“费用超标反而降低成本压力”的符号问题。
|
|
456
|
+
|
|
457
|
+
**DAG-Assign(依赖感知任务分配)**
|
|
458
|
+
|
|
459
|
+
DAG-Assign 使用 Kahn 拓扑排序,拒绝未知节点边和环;关键度定义为唯一后继数量,并为
|
|
460
|
+
synthesis 节点额外加 `100`。每个节点保留 QCG Pareto 候选,然后以 Beam 宽度 256 做
|
|
461
|
+
依赖感知分配,加入依赖交接惩罚、synthesis 质量奖励和关键度奖励。预算剪枝使用后缀最低
|
|
462
|
+
费用;如果预算不可能满足,结果显式报告 `budgetFeasible: false`,而不是静默分配低于质量
|
|
463
|
+
下限的模型。
|
|
464
|
+
|
|
465
|
+
### 7. 复杂度与正确性性质
|
|
466
|
+
|
|
467
|
+
令 $N=\lvert M\rvert$、$K\le 12$ 为剪枝后的候选数、
|
|
468
|
+
$P=\lvert I\rvert$、$W=256$ 为 Beam 宽度,
|
|
469
|
+
当前实现的有界最坏情况复杂度为:
|
|
470
|
+
|
|
471
|
+
| 组件 | 时间复杂度 | 空间复杂度 |
|
|
472
|
+
|---|---:|---:|
|
|
473
|
+
| 候选评分 | $O(PN)$ | $O(PN)$ |
|
|
474
|
+
| 两两 Pareto 剪枝 | $O(PN^2)$ | $O(PN)$ |
|
|
475
|
+
| Beam 分配 | $O(PWK)$ | $O(WK+P)$ |
|
|
476
|
+
| DAG 拓扑排序 | $O(\lvert V\rvert+\lvert E\rvert)$ | $O(\lvert V\rvert+\lvert E\rvert)$ |
|
|
477
|
+
|
|
478
|
+
桌面端模型目录通常较小,且所有循环都受已发现路由数、每包 12 个候选和 Beam 宽度 256
|
|
479
|
+
限制,适合交互式生成计划。
|
|
480
|
+
|
|
481
|
+
系统强制并在结果中公开以下不变量:
|
|
482
|
+
|
|
483
|
+
1. **质量保证**:只要某个工作包存在合格候选,正常分配中所有候选都满足 $Q\ge F$。
|
|
484
|
+
2. **预算保证**:标记 `budgetFeasible: true` 的方案,其估算总费用不超过 `B`(以配置的
|
|
485
|
+
token 和价格估算为准)。
|
|
486
|
+
3. **依赖保证**:协作阶段按拓扑顺序输出,并记录模型交接次数。
|
|
487
|
+
4. **确定性**:相同分数按费用和路由 id 稳定决胜;相同输入、目录和设置产生相同方案。
|
|
488
|
+
5. **优雅降级**:没有模型、provider 失败、LiveBench 过期或质量下限不可满足时,都通过
|
|
489
|
+
显式回退元数据表达,不阻塞 Harness 原生请求路径。
|
|
490
|
+
|
|
491
|
+
Beam 求解器是有界的。它为桌面端交互式路由提供可审计、确定性的近似最优启发式,
|
|
492
|
+
并不声称对任意 DAG 都能形式化保证全局最优。增大 Beam 可以提高搜索覆盖率,但会增加
|
|
493
|
+
决策延迟;Pareto 剪枝和后缀费用下界使默认 `W = 256` 保持可用。
|
|
494
|
+
|
|
495
|
+
### 8. 费用与审计输出
|
|
496
|
+
|
|
497
|
+
每个计划输出:
|
|
498
|
+
|
|
499
|
+
$$
|
|
500
|
+
\begin{aligned}
|
|
501
|
+
\mathrm{TotalCost}&=\sum_{i\in I}\mathrm{Cost}(i,\mathrm{assign}(i)),\\
|
|
502
|
+
\mathrm{BaselineCost}&=\text{每个工作包使用当前可用最高质量模型的费用},\\
|
|
503
|
+
\mathrm{EstimatedSaving}&=\max\!\left(0,1-\frac{\mathrm{TotalCost}}{\mathrm{BaselineCost}}\right)
|
|
504
|
+
\end{aligned}
|
|
505
|
+
$$
|
|
506
|
+
|
|
507
|
+
同时输出每阶段 token 估计、缓存读写 token、预测质量、质量下限、provider/model、Pareto
|
|
508
|
+
剪枝数量、Beam 宽度、交接次数、预算可行性及约束是否放宽。`/router plan` 和 GAL 路由
|
|
509
|
+
分析面板展示这些可审计字段,但不会展示模型私有思维链。
|
|
510
|
+
|
|
511
|
+
算法已接入 Host 请求路径:集体模式由 `index.mjs` 调用 `buildPlan` 并按计划执行;单独
|
|
512
|
+
模式保留用户明确选择的模型,不被集体路由覆盖。回归测试覆盖复杂度、混合业务拆分、
|
|
513
|
+
LiveBench/价格覆盖、预算行为、Pareto 剪枝、AMO 反馈方向、DAG 顺序、多阶段执行和最终
|
|
514
|
+
整合。
|
|
225
515
|
|
|
226
516
|
## OpenCode Zen 设置
|
|
227
517
|
|
package/cordis.patch.yml
CHANGED
|
@@ -3,11 +3,26 @@
|
|
|
3
3
|
- insert:
|
|
4
4
|
- id: modlens
|
|
5
5
|
name: '@liustack/modlens'
|
|
6
|
+
# ModSearch replaces the native web_search provider and adds read_page/x_search.
|
|
7
|
+
- id: modsearch
|
|
8
|
+
name: '@liustack/modsearch'
|
|
9
|
+
# Ego Browser supplies visible Chromium tools for JS-heavy and anti-bot pages.
|
|
10
|
+
- id: ego-browser
|
|
11
|
+
name: 'dsh-ego-browser'
|
|
12
|
+
# The router annotates multi-task approval requests; dsh-approval-gate
|
|
13
|
+
# owns the Flash decision, human handoff, audit and snapshot UI.
|
|
14
|
+
- id: dsh-approval-gate
|
|
15
|
+
name: 'dsh-approval-gate'
|
|
6
16
|
# The package dependency is transitive from the profile's point of view,
|
|
7
17
|
# so this bundle entry is needed when the router is installed by itself.
|
|
8
18
|
- id: model-router-galgame
|
|
9
19
|
name: '@ljwei-stak/model-router-galgame'
|
|
10
20
|
|
|
21
|
+
# Route DSH's native web_search seam through ModSearch's engine chain.
|
|
22
|
+
- id: web
|
|
23
|
+
config:
|
|
24
|
+
searchProvider: modsearch
|
|
25
|
+
|
|
11
26
|
# ModLens consumes the durable image blocks on the Host pre-step. Leave the
|
|
12
27
|
# describe-image fallback available for explicit use, but do not rewrite every
|
|
13
28
|
# text-model upload into a markdown tool reference before ModLens can read it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ljwei-stak/model-router-galgame",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.14",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DeepSeek Harness plugin: adaptive model routing, cost analysis and GAL view",
|
|
6
6
|
"repository": {
|
|
@@ -48,7 +48,12 @@
|
|
|
48
48
|
"node": ">=22.19"
|
|
49
49
|
},
|
|
50
50
|
"dependencies": {
|
|
51
|
-
"
|
|
51
|
+
"dsh-approval-gate": "0.5.0",
|
|
52
|
+
"@deepseek-ai/dsh-tools": "0.1.0-rc.8",
|
|
53
|
+
"@liustack/modsearch": "5.10.1",
|
|
54
|
+
"dsh-ego-browser": "0.8.0",
|
|
55
|
+
"@liustack/modlens": "3.25.4",
|
|
56
|
+
"schemastery": "3.18.0"
|
|
52
57
|
},
|
|
53
58
|
"peerDependencies": {
|
|
54
59
|
"@deepseek-ai/dsh-settings": "^0.1.1-rc.1",
|