codebee 0.1.20 → 0.1.22
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/CHANGELOG.md +171 -165
- package/README.md +36 -9
- package/app/core/automation.py +6 -6
- package/app/core/capability.py +16 -2
- package/app/core/catalog.py +1 -1
- package/app/core/covergen.py +40 -4
- package/app/core/dispatch.py +141 -0
- package/app/core/flows.py +5 -0
- package/app/core/jobs.py +281 -166
- package/app/core/knowledge.py +19 -7
- package/app/core/manager.py +30 -23
- package/app/core/market_remote.py +66 -22
- package/app/core/modelhub.py +145 -11
- package/app/core/pipeline.py +124 -31
- package/app/core/router.py +57 -12
- package/app/core/runner.py +2 -1
- package/app/core/selfupdate.py +24 -14
- package/app/core/settings.py +3 -3
- package/app/core/skills.py +15 -11
- package/app/core/store.py +20 -14
- package/app/core/task_compile.py +83 -0
- package/app/core/zentao.py +26 -6
- package/app/main.py +20 -12
- package/app/pet.py +127 -30
- package/app/ui/app.js +197 -76
- package/app/ui/i18n.js +22 -3
- package/app/ui/index.html +11 -11
- package/app/ui/style.css +32 -0
- package/package.json +1 -1
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""统一调度评分:任务画像、CLI 亲和度与模型链排序。
|
|
3
|
+
|
|
4
|
+
本模块只做纯计算,不读写配置和运行状态。调用方先完成协议、启停、健康、
|
|
5
|
+
密钥等硬约束过滤,再把可用候选交给这里评分;相同分数保持用户原顺序。
|
|
6
|
+
"""
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import math
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
TYPE_DIMENSIONS = {
|
|
13
|
+
"direct": "reasoning",
|
|
14
|
+
"code": "coding",
|
|
15
|
+
"novel": "writing",
|
|
16
|
+
"serial_novel": "writing",
|
|
17
|
+
"article": "writing",
|
|
18
|
+
"video_script": "writing",
|
|
19
|
+
"doc": "writing",
|
|
20
|
+
"translation": "writing",
|
|
21
|
+
"rank_scan": "reasoning",
|
|
22
|
+
"research": "reasoning",
|
|
23
|
+
"speech": "writing",
|
|
24
|
+
"weekly_report": "writing",
|
|
25
|
+
"email": "writing",
|
|
26
|
+
"tech_proposal": "reasoning",
|
|
27
|
+
"resume": "writing",
|
|
28
|
+
"zentao": "coding",
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
_KIND_AFFINITY = {
|
|
32
|
+
"coding": {"codex": 10, "aider": 8, "opencode": 7, "qwen": 6,
|
|
33
|
+
"claude": 5, "generic": 2},
|
|
34
|
+
"writing": {"claude": 10, "qwen": 7, "generic": 5, "codex": 3,
|
|
35
|
+
"opencode": 2, "aider": 0},
|
|
36
|
+
"reasoning": {"claude": 9, "codex": 8, "qwen": 6, "opencode": 4,
|
|
37
|
+
"generic": 3, "aider": 1},
|
|
38
|
+
"vision": {"codex": 10, "claude": 8, "qwen": 5, "opencode": 2,
|
|
39
|
+
"generic": 1, "aider": 0},
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
_TIER_SCORE = {
|
|
43
|
+
"easy": {"budget": 18.0, "standard": 8.0, "premium": -6.0},
|
|
44
|
+
"default": {"budget": 2.0, "standard": 6.0, "premium": 5.0},
|
|
45
|
+
"hard": {"budget": -8.0, "standard": 4.0, "premium": 12.0},
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def task_dimension(task_type, role=""):
|
|
50
|
+
"""把预置任务与步骤角色归一为 writing/coding/reasoning/vision。"""
|
|
51
|
+
ttype = str(task_type or "").strip().lower()
|
|
52
|
+
role = str(role or "").strip().lower()
|
|
53
|
+
if ttype in ("writing", "coding", "reasoning", "vision"):
|
|
54
|
+
return ttype
|
|
55
|
+
if role in ("plan", "review", "critique", "selector", "qa") \
|
|
56
|
+
or "review" in role or "critique" in role:
|
|
57
|
+
return "reasoning"
|
|
58
|
+
return TYPE_DIMENSIONS.get(ttype, "reasoning")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def agent_affinity(kind, task_type, role=""):
|
|
62
|
+
"""CLI 类型对任务维度的温和偏好;只作加分,不覆盖健康与历史信号。"""
|
|
63
|
+
dim = task_dimension(task_type, role)
|
|
64
|
+
score = float((_KIND_AFFINITY.get(dim) or {}).get(kind, 0))
|
|
65
|
+
if role == "review" or "critique" in str(role or ""):
|
|
66
|
+
score += {"claude": 4, "codex": 3, "qwen": 2}.get(kind, 0)
|
|
67
|
+
return score, "%s 匹配 %+.1f" % (dim, score)
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _model_meta(provider, model):
|
|
71
|
+
for item in (provider or {}).get("models") or []:
|
|
72
|
+
if isinstance(item, dict) and item.get("name") == model:
|
|
73
|
+
return item
|
|
74
|
+
return {}
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _price_score(pricing, model, difficulty):
|
|
78
|
+
price = (pricing or {}).get(model) or {}
|
|
79
|
+
try:
|
|
80
|
+
# 输出通常比输入贵且更影响整步成本,按 2 倍权重估算。
|
|
81
|
+
blended = max(0.0, float(price.get("in") or 0.0)
|
|
82
|
+
+ 2.0 * float(price.get("out") or 0.0))
|
|
83
|
+
except (TypeError, ValueError):
|
|
84
|
+
return 0.0, "价格未知"
|
|
85
|
+
if blended <= 0:
|
|
86
|
+
return 0.0, "价格未知"
|
|
87
|
+
magnitude = max(0.0, math.log10(blended + 1.0))
|
|
88
|
+
score = -min(18.0, magnitude * (7.0 if difficulty == "easy" else 2.0))
|
|
89
|
+
return score, "估算价 %.3g(%+.1f)" % (blended, score)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def score_model_entry(entry, providers, pricing, difficulty, task_type="", role=""):
|
|
93
|
+
"""给已通过硬约束的模型链条目评分,返回 (score, explanation)。"""
|
|
94
|
+
provider = (providers or {}).get(entry.get("provider_id")) \
|
|
95
|
+
or entry.get("provider") or {}
|
|
96
|
+
model = entry.get("model") or ""
|
|
97
|
+
meta = _model_meta(provider, model)
|
|
98
|
+
try:
|
|
99
|
+
priority = max(1, int(meta.get("priority") or 99))
|
|
100
|
+
except (TypeError, ValueError):
|
|
101
|
+
priority = 99
|
|
102
|
+
quality = 0.0 if priority == 99 else max(-8.0, 20.0 - (priority - 1) * 4.0)
|
|
103
|
+
tier = meta.get("tier") or provider.get("tier") or "standard"
|
|
104
|
+
tier_score = (_TIER_SCORE.get(difficulty) or {}).get(tier, 0.0)
|
|
105
|
+
price_score, price_reason = _price_score(pricing, model, difficulty)
|
|
106
|
+
dim = task_dimension(task_type, role)
|
|
107
|
+
strengths = provider.get("strengths") if isinstance(provider.get("strengths"), list) else []
|
|
108
|
+
strength_score = 10.0 if dim in strengths else 0.0
|
|
109
|
+
if difficulty == "easy":
|
|
110
|
+
quality *= 0.25
|
|
111
|
+
elif difficulty != "hard":
|
|
112
|
+
# 普通任务兼顾质量、成本与能力匹配;是否真的重排由调用方决定,
|
|
113
|
+
# 因而手工链仍可保持原顺序,自动推荐则能使用这组平衡分。
|
|
114
|
+
quality *= 0.65
|
|
115
|
+
vision_score = 0.0
|
|
116
|
+
if dim == "vision":
|
|
117
|
+
vision_score = 24.0 if meta.get("image_in") else -24.0
|
|
118
|
+
total = quality + tier_score + price_score + strength_score + vision_score
|
|
119
|
+
reason = ("质量 %+.1f,档位 %s %+.1f,%s,能力 %s %+.1f"
|
|
120
|
+
% (quality, tier, tier_score, price_reason, dim,
|
|
121
|
+
strength_score + vision_score))
|
|
122
|
+
return round(total, 2), reason
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def rank_model_entries(entries, providers, pricing, difficulty,
|
|
126
|
+
task_type="", role="", force=False):
|
|
127
|
+
"""稳定排序模型链并返回脱敏决策明细;default 难度保持人工顺序。"""
|
|
128
|
+
rows = []
|
|
129
|
+
for index, entry in enumerate(entries or []):
|
|
130
|
+
score, reason = score_model_entry(
|
|
131
|
+
entry, providers, pricing, difficulty, task_type, role)
|
|
132
|
+
rows.append((score, index, entry, reason))
|
|
133
|
+
if difficulty in ("easy", "hard") or force:
|
|
134
|
+
rows.sort(key=lambda row: (-row[0], row[1]))
|
|
135
|
+
ranked = [row[2] for row in rows]
|
|
136
|
+
decisions = [{"provider_id": row[2].get("provider_id") or "",
|
|
137
|
+
"provider": (row[2].get("provider") or {}).get("name") or "",
|
|
138
|
+
"model": row[2].get("model") or "",
|
|
139
|
+
"score": row[0], "reason": row[3]}
|
|
140
|
+
for row in rows]
|
|
141
|
+
return ranked, decisions
|
package/app/core/flows.py
CHANGED
|
@@ -187,6 +187,11 @@ def _apply_overrides(base, ov):
|
|
|
187
187
|
f["rounds"] = max(1, min(5, int(v)))
|
|
188
188
|
except Exception:
|
|
189
189
|
pass
|
|
190
|
+
elif k == "best_of":
|
|
191
|
+
try:
|
|
192
|
+
f["best_of"] = max(1, min(3, int(v)))
|
|
193
|
+
except Exception:
|
|
194
|
+
pass
|
|
190
195
|
elif k in ("manuscript", "verify_command"):
|
|
191
196
|
s = re.sub(r"[\\/]+", "_", str(v or "")).strip()
|
|
192
197
|
s = re.sub(r"\.{2,}", "_", s).lstrip(".")
|