abelien-harness 0.5.0 → 0.6.0
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/.env.example +1 -0
- package/README.md +11 -18
- package/package.json +2 -3
- package/skills/abelien-appearance/SKILL.md +1 -0
- package/skills/abelien-clarify/SKILL.md +12 -2
- package/skills/abelien-clarify/references/examples.md +22 -0
- package/skills/abelien-clarify/scripts/check_result.py +38 -3
- package/skills/abelien-clarify/scripts/test_check_result.py +32 -1
- package/skills/abelien-guide/SKILL.md +1 -1
- package/skills/abelien-hardware/SKILL.md +4 -4
- package/skills/abelien-hardware/domain.json +1 -1
- package/skills/abelien-hardware/references/search-index-format.md +4 -2
- package/skills/abelien-hardware/references/web-evidence-contract.md +3 -3
- package/skills/abelien-hardware/scripts/dimensions.mjs +23 -8
- package/skills/abelien-hardware/scripts/electrical.mjs +62 -19
- package/skills/abelien-hardware/scripts/finalize-selection.mjs +3 -5
- package/skills/abelien-hardware/scripts/provider-chain-contract.mjs +17 -0
- package/skills/abelien-hardware/scripts/public-page.mjs +11 -121
- package/skills/abelien-hardware/scripts/response-redaction.mjs +1 -130
- package/skills/abelien-hardware/scripts/selection-request.mjs +6 -5
- package/skills/abelien-hardware/scripts/tos-wiki-search.mjs +9 -6
- package/skills/abelien-hardware/scripts/volc-web-search.mjs +10 -32
- package/skills/abelien-hardware/scripts/web-common.mjs +4 -10
- package/skills/abelien-hardware/scripts/web-integrity.mjs +1 -1
- package/skills/abelien-hardware/scripts/web-provider-chain.mjs +2 -1
- package/skills/abelien-hardware/scripts/web-receipt.mjs +4 -2
- package/skills/abelien-hardware/scripts/web-search-plan.mjs +5 -1
- package/skills/abelien-hardware/scripts/web-search.mjs +4 -3
- package/skills/abelien-hardware-selection/domain.json +2 -2
- package/skills/abelien-hardware-selection/references/selection-service-contract.md +18 -10
- package/skills/abelien-hardware-selection/scripts/select.mjs +13 -5
- package/skills/abelien-harness/scripts/ab.mjs +24 -2
- package/skills/abelien-harness/scripts/web/common.mjs +20 -0
- package/skills/abelien-harness/scripts/web/public-read.mjs +168 -0
- package/skills/abelien-harness/scripts/web/redact.mjs +136 -0
- package/skills/abelien-harness/scripts/web/volc.mjs +49 -0
- package/skills/abelien-reference/SKILL.md +51 -0
- package/skills/abelien-reference/domain.json +92 -0
- package/skills/abelien-reference/fixtures/create.final.json +102 -0
- package/skills/abelien-reference/fixtures/restore.collected.json +189 -0
- package/skills/abelien-reference/fixtures/restore.final.json +267 -0
- package/skills/abelien-reference/references/research-contract.md +74 -0
- package/skills/abelien-reference/schemas/reference-evidence.v1.json +140 -0
- package/skills/abelien-reference/scripts/evidence.mjs +60 -0
- package/skills/abelien-reference/scripts/finalize.mjs +116 -0
- package/skills/abelien-reference/scripts/measure.mjs +62 -0
- package/skills/abelien-reference/scripts/png.mjs +86 -0
- package/skills/abelien-reference/scripts/research.mjs +179 -0
- package/skills/abelien-structure/SKILL.md +2 -2
- package/skills/abelien-structure/domain.json +64 -0
- package/skills/abelien-structure/references/build123d-execution.md +1 -1
- package/skills/abelien-structure/references/handoff.md +1 -1
- package/skills/abelien-structure/worker/abelien_cad.py +585 -0
- package/skills/abelien-structure/worker/assembly-validation.mjs +106 -0
- package/skills/abelien-structure/worker/cad.py +1623 -0
- package/skills/abelien-structure/worker/cli-adapter.mjs +207 -0
- package/skills/abelien-structure/worker/component-fit-glb.mjs +173 -0
- package/skills/abelien-structure/worker/component-fit.d.mts +82 -0
- package/skills/abelien-structure/worker/component-fit.mjs +167 -0
- package/skills/abelien-structure/worker/component-target-image.d.mts +23 -0
- package/skills/abelien-structure/worker/component-target-image.mjs +107 -0
- package/skills/abelien-structure/worker/component-target-render.py +58 -0
- package/skills/abelien-structure/worker/config.mjs +6 -0
- package/skills/abelien-structure/worker/engineering-fit.d.mts +48 -0
- package/skills/abelien-structure/worker/engineering-fit.mjs +118 -0
- package/skills/abelien-structure/worker/engineering-policy.json +16 -0
- package/skills/abelien-structure/worker/engineering-repair.py +147 -0
- package/skills/abelien-structure/worker/engineering.py +324 -0
- package/skills/abelien-structure/worker/identity.mjs +98 -0
- package/skills/abelien-structure/worker/inspection-geometry.mjs +71 -0
- package/skills/abelien-structure/worker/jobs.mjs +451 -0
- package/skills/abelien-structure/worker/library-runtime.mjs +15 -0
- package/skills/abelien-structure/worker/main.mjs +102 -0
- package/skills/abelien-structure/worker/manifest-digest.mjs +11 -0
- package/skills/abelien-structure/worker/materials.mjs +52 -0
- package/skills/abelien-structure/worker/native-launcher.py +13 -0
- package/skills/abelien-structure/worker/native.mjs +157 -0
- package/skills/abelien-structure/worker/representation.mjs +47 -0
- package/skills/abelien-structure/worker/sandbox.mjs +41 -0
- package/skills/abelien-structure/worker/source-contract.mjs +143 -0
- package/skills/abelien-structure/worker/source_call_stats.py +59 -0
- package/skills/abelien-structure/worker/supervisor.mjs +38 -0
- package/skills/abelien-workspace/SKILL.md +11 -0
- package/skills/abelien-workspace/assets/journey-stage-plan.json +155 -0
- package/skills/abelien-workspace/domain.json +25 -2
- package/skills/abelien-workspace/references/hardware-journey-orchestration.md +1 -1
- package/skills/abelien-workspace/references/harness-binding.md +2 -2
- package/skills/abelien-workspace/references/user-journey.md +10 -0
- package/skills/abelien-workspace/scripts/journey-next.mjs +38 -0
- package/skills/abelien-workspace/workflows/definitions/399ef372790b7330d0ef88ce5096e7f69943d4793de97b2ed6c25165a332612f.json +230 -0
- package/skills/abelien-workspace/workflows/definitions/591a7f21e0b0ad586e340163f7533ac910356091ba8d2a26e250c0acbc80cd8b.json +176 -0
- package/skills/abelien-workspace/workflows/definitions/d9f8c6cdf0b2c41cdeb1c72ac5d1e37d95a7caac3272d434a3c7e8483fc565de.json +150 -0
- package/skills/abelien-workspace/workflows/user-journey-hardware-algorithm.json +3 -2
- package/skills/abelien-workspace/workflows/user-journey-hardware-candidates.json +3 -2
- package/skills/abelien-workspace/workflows/user-journey-hardware.json +3 -2
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
{
|
|
2
|
+
"budget": {
|
|
3
|
+
"images": 1,
|
|
4
|
+
"pages": 2,
|
|
5
|
+
"queries": 1
|
|
6
|
+
},
|
|
7
|
+
"changeable": [],
|
|
8
|
+
"depth": "assembly",
|
|
9
|
+
"id": "ref-40ece5312c4ecbf6-fd2b162cb",
|
|
10
|
+
"images": [
|
|
11
|
+
{
|
|
12
|
+
"blob_ref": "sha256:e2273b5b355a38441b20796072e08921b9ab30c4f37bb92070a53307af34a6f6",
|
|
13
|
+
"id": "i1",
|
|
14
|
+
"measurement": {
|
|
15
|
+
"aspect_h_over_w": 1.8188,
|
|
16
|
+
"background": {
|
|
17
|
+
"mode": "color",
|
|
18
|
+
"rgb": [
|
|
19
|
+
255,
|
|
20
|
+
255,
|
|
21
|
+
255
|
|
22
|
+
]
|
|
23
|
+
},
|
|
24
|
+
"bbox": {
|
|
25
|
+
"height": 502,
|
|
26
|
+
"width": 276,
|
|
27
|
+
"x": 62,
|
|
28
|
+
"y": 49
|
|
29
|
+
},
|
|
30
|
+
"image": {
|
|
31
|
+
"height": 600,
|
|
32
|
+
"width": 400
|
|
33
|
+
},
|
|
34
|
+
"method": "silhouette-v1",
|
|
35
|
+
"status": "measured"
|
|
36
|
+
},
|
|
37
|
+
"media_type": "image/png",
|
|
38
|
+
"page_id": "p1",
|
|
39
|
+
"reason": null,
|
|
40
|
+
"sha256": "da0fadd6fd3ab5ccfbac94ca4e3f2b48e440987eee6834f1c30f04c02abd5d11",
|
|
41
|
+
"status": "read",
|
|
42
|
+
"url": "https://img.example.com/pro.png"
|
|
43
|
+
}
|
|
44
|
+
],
|
|
45
|
+
"intent": "restore",
|
|
46
|
+
"items": [
|
|
47
|
+
{
|
|
48
|
+
"id": "u1",
|
|
49
|
+
"image": {
|
|
50
|
+
"blob_ref": "sha256:e2273b5b355a38441b20796072e08921b9ab30c4f37bb92070a53307af34a6f6",
|
|
51
|
+
"media_type": "image/png",
|
|
52
|
+
"sha256": "da0fadd6fd3ab5ccfbac94ca4e3f2b48e440987eee6834f1c30f04c02abd5d11"
|
|
53
|
+
},
|
|
54
|
+
"kind": "user_input",
|
|
55
|
+
"query": null,
|
|
56
|
+
"role": "帮我根据图片还原小爱音箱",
|
|
57
|
+
"source": null,
|
|
58
|
+
"status": "ready",
|
|
59
|
+
"title": "你的输入",
|
|
60
|
+
"verdict": null
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
"id": "p1",
|
|
64
|
+
"image": {
|
|
65
|
+
"blob_ref": "sha256:e2273b5b355a38441b20796072e08921b9ab30c4f37bb92070a53307af34a6f6",
|
|
66
|
+
"media_type": "image/png",
|
|
67
|
+
"sha256": "da0fadd6fd3ab5ccfbac94ca4e3f2b48e440987eee6834f1c30f04c02abd5d11"
|
|
68
|
+
},
|
|
69
|
+
"kind": "candidate",
|
|
70
|
+
"query": "Xiaomi XiaoAI speaker Pro dimensions",
|
|
71
|
+
"role": "候选产品",
|
|
72
|
+
"source": {
|
|
73
|
+
"site": "www.mi.example.com",
|
|
74
|
+
"url": "https://www.mi.example.com/pro/specs"
|
|
75
|
+
},
|
|
76
|
+
"status": "ready",
|
|
77
|
+
"title": "小爱音箱 Pro",
|
|
78
|
+
"verdict": {
|
|
79
|
+
"code": "closest",
|
|
80
|
+
"basis": "选定为「小爱音箱 Pro」;实测高宽比 1.8188,规格 1.822,相差 0.2%"
|
|
81
|
+
}
|
|
82
|
+
},
|
|
83
|
+
{
|
|
84
|
+
"id": "p2",
|
|
85
|
+
"image": null,
|
|
86
|
+
"kind": "candidate",
|
|
87
|
+
"query": "Xiaomi XiaoAI speaker Pro dimensions",
|
|
88
|
+
"role": "候选产品",
|
|
89
|
+
"source": {
|
|
90
|
+
"site": "www.mi.example.com",
|
|
91
|
+
"url": "https://www.mi.example.com/gen1/specs"
|
|
92
|
+
},
|
|
93
|
+
"status": "ready",
|
|
94
|
+
"title": "小米AI音箱",
|
|
95
|
+
"verdict": {
|
|
96
|
+
"code": "ratio_mismatch",
|
|
97
|
+
"basis": "实测高宽比 1.8188,规格 2.4045,相差 24.4%"
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
"id": "sp1",
|
|
102
|
+
"kind": "spec",
|
|
103
|
+
"title": "外形尺寸",
|
|
104
|
+
"role": "Ø118 × 215 mm(引自 p1)",
|
|
105
|
+
"source": {
|
|
106
|
+
"url": "https://www.mi.example.com/pro/specs",
|
|
107
|
+
"site": "www.mi.example.com"
|
|
108
|
+
},
|
|
109
|
+
"image": null,
|
|
110
|
+
"verdict": null,
|
|
111
|
+
"status": "ready",
|
|
112
|
+
"query": null
|
|
113
|
+
}
|
|
114
|
+
],
|
|
115
|
+
"keep": [
|
|
116
|
+
"外形尺寸与比例"
|
|
117
|
+
],
|
|
118
|
+
"pages": [
|
|
119
|
+
{
|
|
120
|
+
"excerpt": "小爱音箱 Pro 参数 产品尺寸:118 × 118 × 215 mm,重量 1187 g。",
|
|
121
|
+
"final_url": "https://www.mi.example.com/pro/specs",
|
|
122
|
+
"id": "p1",
|
|
123
|
+
"image_url": "https://img.example.com/pro.png",
|
|
124
|
+
"purpose": "identify",
|
|
125
|
+
"raw_sha256": "109cbac90dd51ef3dddfd21c4a97168a25f097b4211b2146d21669281d06e3b8",
|
|
126
|
+
"reason": null,
|
|
127
|
+
"search_id": "s1",
|
|
128
|
+
"status": "read",
|
|
129
|
+
"text_blob_ref": "sha256:a9d6a6d8668de3346efe1d94c1afd76e71faf37bee5a6e55d3a82f9bc60b9bf0",
|
|
130
|
+
"text_sha256": "bae09c2d8ac69290d90e7cf69d7809858b1149a391400d40030037db41896415",
|
|
131
|
+
"title": "小爱音箱 Pro 参数",
|
|
132
|
+
"url": "https://www.mi.example.com/pro/specs"
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"excerpt": "小米AI音箱 规格 尺寸 88 mm × 88 mm × 211.6 mm",
|
|
136
|
+
"final_url": "https://www.mi.example.com/gen1/specs",
|
|
137
|
+
"id": "p2",
|
|
138
|
+
"image_url": null,
|
|
139
|
+
"purpose": "identify",
|
|
140
|
+
"raw_sha256": "c139f165c41dc34c2cd30a816a80d7cbef9ecbda49e7496d46163c8692da33c7",
|
|
141
|
+
"reason": null,
|
|
142
|
+
"search_id": "s1",
|
|
143
|
+
"status": "read",
|
|
144
|
+
"text_blob_ref": "sha256:3dd43ad1e906a2e95af747cc8f8386c00b43df981af8b8bafe45bb2d173e7e3c",
|
|
145
|
+
"text_sha256": "2de9e3c9406bd62a2e9eef887fd42008382616377ac49fb751cd5a109f1e9a00",
|
|
146
|
+
"title": "小米AI音箱 规格",
|
|
147
|
+
"url": "https://www.mi.example.com/gen1/specs"
|
|
148
|
+
}
|
|
149
|
+
],
|
|
150
|
+
"request_digest": "40ece5312c4ecbf6f5548d36589de25734706b8a333d22e1a3c32adeb75e09f5",
|
|
151
|
+
"schema": "abelien.reference-evidence.v1",
|
|
152
|
+
"searches": [
|
|
153
|
+
{
|
|
154
|
+
"count": 5,
|
|
155
|
+
"error_code": null,
|
|
156
|
+
"id": "s1",
|
|
157
|
+
"purpose": "identify",
|
|
158
|
+
"query": "Xiaomi XiaoAI speaker Pro dimensions",
|
|
159
|
+
"receipt_sha256": "099206543e4389fde6c4838d3fc0b125dce0fb98cd57384db3062e1abcede7a8",
|
|
160
|
+
"request_id": "req-[redacted]",
|
|
161
|
+
"results": [
|
|
162
|
+
{
|
|
163
|
+
"rank": 1,
|
|
164
|
+
"snippet": "",
|
|
165
|
+
"title": "XiaoAI Speaker Pro",
|
|
166
|
+
"url": "https://www.mi.example.com/pro/specs"
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
"rank": 2,
|
|
170
|
+
"snippet": "",
|
|
171
|
+
"title": "Mi AI Speaker",
|
|
172
|
+
"url": "https://www.mi.example.com/gen1/specs"
|
|
173
|
+
}
|
|
174
|
+
],
|
|
175
|
+
"status": "completed"
|
|
176
|
+
}
|
|
177
|
+
],
|
|
178
|
+
"status": "final",
|
|
179
|
+
"text": "帮我根据图片还原小爱音箱",
|
|
180
|
+
"unknowns": [],
|
|
181
|
+
"user_image": {
|
|
182
|
+
"blob_ref": "sha256:e2273b5b355a38441b20796072e08921b9ab30c4f37bb92070a53307af34a6f6",
|
|
183
|
+
"measurement": {
|
|
184
|
+
"aspect_h_over_w": 1.8188,
|
|
185
|
+
"background": {
|
|
186
|
+
"mode": "color",
|
|
187
|
+
"rgb": [
|
|
188
|
+
255,
|
|
189
|
+
255,
|
|
190
|
+
255
|
|
191
|
+
]
|
|
192
|
+
},
|
|
193
|
+
"bbox": {
|
|
194
|
+
"height": 502,
|
|
195
|
+
"width": 276,
|
|
196
|
+
"x": 62,
|
|
197
|
+
"y": 49
|
|
198
|
+
},
|
|
199
|
+
"image": {
|
|
200
|
+
"height": 600,
|
|
201
|
+
"width": 400
|
|
202
|
+
},
|
|
203
|
+
"method": "silhouette-v1",
|
|
204
|
+
"status": "measured"
|
|
205
|
+
},
|
|
206
|
+
"media_type": "image/png",
|
|
207
|
+
"sha256": "da0fadd6fd3ab5ccfbac94ca4e3f2b48e440987eee6834f1c30f04c02abd5d11"
|
|
208
|
+
},
|
|
209
|
+
"collected_id": "ref-40ece5312c4ecbf6",
|
|
210
|
+
"judgement_digest": "d2b162cb00ce8cc52049f321226a89e4e0328b55d54fb022fb4a4539088882c0",
|
|
211
|
+
"identity": {
|
|
212
|
+
"candidates": [
|
|
213
|
+
{
|
|
214
|
+
"item_id": "p1",
|
|
215
|
+
"name": "小爱音箱 Pro",
|
|
216
|
+
"dimensions_mm": {
|
|
217
|
+
"width": 118,
|
|
218
|
+
"depth": 118,
|
|
219
|
+
"height": 215
|
|
220
|
+
},
|
|
221
|
+
"quote": {
|
|
222
|
+
"page_id": "p1",
|
|
223
|
+
"text": "产品尺寸:118 × 118 × 215 mm"
|
|
224
|
+
},
|
|
225
|
+
"ratio_check": {
|
|
226
|
+
"status": "consistent",
|
|
227
|
+
"measured": 1.8188,
|
|
228
|
+
"spec": 1.822,
|
|
229
|
+
"relative_error": 0.0018,
|
|
230
|
+
"tolerance": 0.1
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
{
|
|
234
|
+
"item_id": "p2",
|
|
235
|
+
"name": "小米AI音箱",
|
|
236
|
+
"dimensions_mm": {
|
|
237
|
+
"width": 88,
|
|
238
|
+
"depth": 88,
|
|
239
|
+
"height": 211.6
|
|
240
|
+
},
|
|
241
|
+
"quote": {
|
|
242
|
+
"page_id": "p2",
|
|
243
|
+
"text": "尺寸 88 mm × 88 mm × 211.6 mm"
|
|
244
|
+
},
|
|
245
|
+
"ratio_check": {
|
|
246
|
+
"status": "mismatch",
|
|
247
|
+
"measured": 1.8188,
|
|
248
|
+
"spec": 2.4045,
|
|
249
|
+
"relative_error": 0.2436,
|
|
250
|
+
"tolerance": 0.1
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
],
|
|
254
|
+
"chosen_item_id": "p1",
|
|
255
|
+
"conflict": null
|
|
256
|
+
},
|
|
257
|
+
"similar": [],
|
|
258
|
+
"spec_quotes": [
|
|
259
|
+
{
|
|
260
|
+
"id": "sp1",
|
|
261
|
+
"quote": {
|
|
262
|
+
"page_id": "p1",
|
|
263
|
+
"text": "118 × 118 × 215 mm"
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
]
|
|
267
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# 参考研究契约(reference.research / reference.finalize)
|
|
2
|
+
|
|
3
|
+
本文是 `abelien-reference` 两个 capability 的输入、输出、上限与错误码的正文。数据格式以 `schemas/reference-evidence.v1.json` 为准,样例见 `fixtures/`;判定逻辑在 `scripts/` 中,并有测试覆盖。
|
|
4
|
+
|
|
5
|
+
## reference.research
|
|
6
|
+
|
|
7
|
+
### 输入
|
|
8
|
+
|
|
9
|
+
只接受下列字段,出现其他字段一律 `VALIDATION`:
|
|
10
|
+
|
|
11
|
+
| 字段 | 规则 |
|
|
12
|
+
| --- | --- |
|
|
13
|
+
| `intent` | `restore` / `create` / `modify`,取自需求的 `intent.kind`(clarify 判定) |
|
|
14
|
+
| `keep` / `changeable` | 保留项与可改项,各最多 20 条,每条 1–500 字 |
|
|
15
|
+
| `depth` | `appearance` / `assembly` / `functional`,或 `null` |
|
|
16
|
+
| `text` | 用户原话,1–4000 字 |
|
|
17
|
+
| `links` | 最多 5 个公开 HTTPS 链接,不得带凭据或指向内网 |
|
|
18
|
+
| `user_image` | 可选,`{media_type: "image/png", base64}`,不超过 16MB;其他格式由宿主先转成 PNG |
|
|
19
|
+
| `plan.queries` | 最多 6 条,每条为 `{purpose: identify / teardown / similar, query, count?: 1–5}` |
|
|
20
|
+
| `plan.max_pages` / `plan.max_images` | 页面最多读几页(0–12,默认 8)、主图最多抓几张(0–8,默认 6) |
|
|
21
|
+
|
|
22
|
+
### 执行
|
|
23
|
+
|
|
24
|
+
- **检索**:只用宿主环境变量 `WEB_SEARCH_API_KEY` 调 Volc。原始回执先脱敏,再计算摘要。单条检索失败时,该条记为 `failed`,并写明错误码,其余照常进行;缺少 key 时,在发出任何请求之前就以 `REFERENCE_WEB_CONFIG` 失败。
|
|
25
|
+
- **页面**:先读链接,再从各条检索结果中轮流取页面,避免某一条检索占满读取预算。读取只走内核的安全直读:只读公网 HTTPS,每一跳都重新解析并钉住地址,限制重定向次数。每页最多读 8MB,超出时记为 `too_large`、不可用。正文存为内容 blob,资源里只保留前 1200 字的摘录。
|
|
26
|
+
- **主图**:取页面的 `og:image`、`twitter:image` 或 `image_src`。图片同样走钉住地址的直读,每张不超过 4MB,只接受 PNG、JPEG、WebP,原始字节存为内容 blob。其中的 PNG 会额外测量轮廓。
|
|
27
|
+
- **用户原图**:存为内容 blob 并测量轮廓(`silhouette-v1`),得到包围盒和高宽比;背景必须是单色或透明。背景杂乱、没有主体、隔行 PNG、超大图都记为 `unknown`,并写明原因。
|
|
28
|
+
- **幂等**:结果 id 由完整请求摘要(包括原图哈希)派生。已有同 id 的收集结果时直接返回,不再检索、不再读取。因此单条检索或页面的临时失败不会因为原样重试而自动恢复;要重试,就改写检索计划(比如换个检索词、改结果数),形成新的请求。
|
|
29
|
+
|
|
30
|
+
### 输出
|
|
31
|
+
|
|
32
|
+
输出 `reference-evidence`,status=collected。`items` 中第一项是 `user_input`,其后每个页面对应一格:识别用途为 `candidate`,逆向用途为 `teardown`,相似用途为 `similar`。读不到的页面状态为 `unavailable`。此时还没有任何 `verdict`。
|
|
33
|
+
|
|
34
|
+
## reference.finalize
|
|
35
|
+
|
|
36
|
+
### 输入
|
|
37
|
+
|
|
38
|
+
| 字段 | 规则 |
|
|
39
|
+
| --- | --- |
|
|
40
|
+
| `evidence_id` | 必须指向一份 status=collected 的 `reference-evidence:ref-…` |
|
|
41
|
+
| `identity` | 可选,`{candidates: [{item_id, name, dimensions_mm?: {width, depth?, height}, quote: {page_id, text}}], chosen_item_id}`,最多 6 个候选;只要有候选,`chosen_item_id` 就必须是其中之一。引用是必填的识别依据;页面没有给出尺寸时,可以不填 `dimensions_mm`,此时比例记为未核对 |
|
|
42
|
+
| `similar` | 可选,最多 12 条,每条为 `{item_id, decision: borrow / skip, borrowed?, why, not_copied?}`;`borrow` 时必须写明 `borrowed` |
|
|
43
|
+
| `excluded` | 可选,最多 12 条,每条为 `{item_id, reason}` |
|
|
44
|
+
| `specs` | 可选,最多 12 条,每条为 `{title, value, quote}`,生成 `spec` 格 |
|
|
45
|
+
|
|
46
|
+
### 核验
|
|
47
|
+
|
|
48
|
+
- 引用经过空白归一后,必须逐字出现在该页已保存的正文里。只能引用读取成功的页面。
|
|
49
|
+
- 给出尺寸时,宽、高(以及给出时的深)这些数字必须都出现在引用里;没给尺寸时,比例核对为 `unknown`(`no_dimensions`),并写入 `unknowns`。
|
|
50
|
+
- 条目种类必须匹配:候选必须是 `candidate` 格,相似必须是 `similar` 格;排除不能作用于 `user_input`。
|
|
51
|
+
|
|
52
|
+
### 判定(确定性)
|
|
53
|
+
|
|
54
|
+
- 正面视角下,比较高宽比:规格比例为 `height / width`,与用户原图的实测值比较,相对误差不超过 10% 记为 `consistent`,否则记为 `mismatch`;没有测量或缺尺寸时记为 `unknown`。
|
|
55
|
+
- 所选候选:比例一致或无法核对时,判定为 `closest`,依据中写明比例情况;比例不符时,判定为 `ratio_mismatch`,并在 `identity.conflict` 中写明。
|
|
56
|
+
- 其他候选:比例不符时判定为 `ratio_mismatch`,其余不加判定,仍保留为候选,供用户切换。
|
|
57
|
+
- 相似产品:`borrow` 判定为 `borrowed`,依据中写明借用了什么、原因、哪些不照搬;`skip` 判定为 `not_copied`。
|
|
58
|
+
- 排除项:状态改为 `excluded`,判定为 `excluded`。
|
|
59
|
+
- 同一份收集结果可以多次提交不同判断,每次生成一份新的 final 结果(id 带判断摘要),收集结果本身不变。
|
|
60
|
+
|
|
61
|
+
## 错误码
|
|
62
|
+
|
|
63
|
+
| 码 | 含义 |
|
|
64
|
+
| --- | --- |
|
|
65
|
+
| `VALIDATION` | 输入不合规、引用不在原文、尺寸不在引用中、条目种类不符 |
|
|
66
|
+
| `REFERENCE_WEB_CONFIG` | 缺少 `WEB_SEARCH_API_KEY`,或 DNS 配置非法 |
|
|
67
|
+
| `REFERENCE_WEB_*` | 单条检索、读取的失败码,只记在对应的检索、页面或图片里,不使整次运行失败 |
|
|
68
|
+
| `REFERENCE_HOST` | 宿主没有提供可信的资源读取、内容读写端口 |
|
|
69
|
+
|
|
70
|
+
## 宿主责任
|
|
71
|
+
|
|
72
|
+
- 把上传的图片转成 PNG,写入作品目录中的输入文件,再用 `--input-file` 传入。
|
|
73
|
+
- 只展示 `items` 和 `unknowns`;`verdict` 原样展示,不重新解释,也不改写。
|
|
74
|
+
- 第三方图片从内容 blob 转存到私有存储,展示时附来源,不进入图鉴或公开分享。
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
|
3
|
+
"$id": "https://git.abelien.cn/abelien/abelienharness/skills/abelien-reference/schemas/reference-evidence.v1.json",
|
|
4
|
+
"title": "abelien.reference-evidence.v1",
|
|
5
|
+
"description": "参考研究证据。status=collected 由 reference.research 产出,只有收集结果;status=final 由 reference.finalize 产出,带核验过的识别、相似产品与判定。宿主只展示与转存,不自行改写判定。",
|
|
6
|
+
"type": "object",
|
|
7
|
+
"required": ["id", "schema", "status", "intent", "keep", "changeable", "depth", "text", "user_image", "searches", "pages", "images", "items", "unknowns"],
|
|
8
|
+
"properties": {
|
|
9
|
+
"id": { "type": "string", "pattern": "^ref-[0-9a-f]{16}(-f[0-9a-f]{8})?$" },
|
|
10
|
+
"schema": { "const": "abelien.reference-evidence.v1" },
|
|
11
|
+
"status": { "enum": ["collected", "final"] },
|
|
12
|
+
"request_digest": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
13
|
+
"intent": { "enum": ["restore", "create", "modify"] },
|
|
14
|
+
"keep": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
15
|
+
"changeable": { "type": "array", "items": { "type": "string", "minLength": 1 } },
|
|
16
|
+
"depth": { "enum": ["appearance", "assembly", "functional", null] },
|
|
17
|
+
"text": { "type": "string", "minLength": 1 },
|
|
18
|
+
"user_image": {
|
|
19
|
+
"oneOf": [
|
|
20
|
+
{ "type": "null" },
|
|
21
|
+
{
|
|
22
|
+
"type": "object",
|
|
23
|
+
"required": ["blob_ref", "sha256", "media_type", "measurement"],
|
|
24
|
+
"properties": {
|
|
25
|
+
"blob_ref": { "$ref": "#/$defs/blobRef" },
|
|
26
|
+
"sha256": { "$ref": "#/$defs/sha256" },
|
|
27
|
+
"media_type": { "const": "image/png" },
|
|
28
|
+
"measurement": { "$ref": "#/$defs/measurement" }
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
},
|
|
33
|
+
"searches": { "type": "array", "items": { "$ref": "#/$defs/search" } },
|
|
34
|
+
"pages": { "type": "array", "items": { "$ref": "#/$defs/page" } },
|
|
35
|
+
"images": { "type": "array", "items": { "$ref": "#/$defs/image" } },
|
|
36
|
+
"items": { "type": "array", "items": { "$ref": "#/$defs/item" } },
|
|
37
|
+
"unknowns": { "type": "array", "items": { "type": "string" } },
|
|
38
|
+
"collected_id": { "type": "string", "description": "status=final 时为来源收集结果的 id" },
|
|
39
|
+
"judgement_digest": { "type": "string" },
|
|
40
|
+
"identity": {
|
|
41
|
+
"description": "status=final;还原与改现成时为识别判断,新创时为 null",
|
|
42
|
+
"oneOf": [
|
|
43
|
+
{ "type": "null" },
|
|
44
|
+
{
|
|
45
|
+
"type": "object",
|
|
46
|
+
"required": ["candidates", "chosen_item_id", "conflict"],
|
|
47
|
+
"properties": {
|
|
48
|
+
"candidates": { "type": "array", "items": { "$ref": "#/$defs/candidate" } },
|
|
49
|
+
"chosen_item_id": { "type": ["string", "null"] },
|
|
50
|
+
"conflict": { "type": ["string", "null"] }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"similar": { "type": "array", "items": { "$ref": "#/$defs/similar" } }
|
|
56
|
+
},
|
|
57
|
+
"$defs": {
|
|
58
|
+
"sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" },
|
|
59
|
+
"blobRef": { "type": "string", "pattern": "^sha256:[0-9a-f]{64}$" },
|
|
60
|
+
"measurement": {
|
|
61
|
+
"type": "object",
|
|
62
|
+
"required": ["status", "method"],
|
|
63
|
+
"properties": {
|
|
64
|
+
"status": { "enum": ["measured", "unknown"] },
|
|
65
|
+
"method": { "const": "silhouette-v1" },
|
|
66
|
+
"reason": { "type": "string" },
|
|
67
|
+
"aspect_h_over_w": { "type": "number" },
|
|
68
|
+
"bbox": { "type": "object" },
|
|
69
|
+
"background": { "type": "object" }
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"search": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"required": ["id", "purpose", "query", "count", "status", "results"],
|
|
75
|
+
"properties": {
|
|
76
|
+
"purpose": { "enum": ["identify", "teardown", "similar"] },
|
|
77
|
+
"status": { "enum": ["completed", "failed"] },
|
|
78
|
+
"error_code": { "type": ["string", "null"] },
|
|
79
|
+
"results": { "type": "array", "items": { "type": "object", "required": ["rank", "url", "title"] } }
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
"page": {
|
|
83
|
+
"type": "object",
|
|
84
|
+
"required": ["id", "url", "purpose", "status"],
|
|
85
|
+
"properties": {
|
|
86
|
+
"status": { "enum": ["read", "unavailable"] },
|
|
87
|
+
"text_blob_ref": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/blobRef" }] },
|
|
88
|
+
"excerpt": { "type": "string" },
|
|
89
|
+
"image_url": { "type": ["string", "null"] }
|
|
90
|
+
}
|
|
91
|
+
},
|
|
92
|
+
"image": {
|
|
93
|
+
"type": "object",
|
|
94
|
+
"required": ["id", "url", "page_id", "status"],
|
|
95
|
+
"properties": {
|
|
96
|
+
"status": { "enum": ["read", "unavailable"] },
|
|
97
|
+
"blob_ref": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/blobRef" }] },
|
|
98
|
+
"media_type": { "type": ["string", "null"] }
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"verdict": {
|
|
102
|
+
"type": "object",
|
|
103
|
+
"required": ["code", "basis"],
|
|
104
|
+
"properties": {
|
|
105
|
+
"code": { "enum": ["closest", "ratio_mismatch", "excluded", "borrowed", "not_copied", "fits", "envelope"] },
|
|
106
|
+
"basis": { "type": "string", "minLength": 1 }
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"item": {
|
|
110
|
+
"type": "object",
|
|
111
|
+
"description": "画布网格中的一格。verdict 只由 reference.finalize 的确定性检查给出。",
|
|
112
|
+
"required": ["id", "kind", "title", "role", "source", "image", "verdict", "status", "query"],
|
|
113
|
+
"properties": {
|
|
114
|
+
"id": { "type": "string", "minLength": 1 },
|
|
115
|
+
"kind": { "enum": ["user_input", "candidate", "teardown", "spec", "similar"] },
|
|
116
|
+
"title": { "type": "string" },
|
|
117
|
+
"role": { "type": "string" },
|
|
118
|
+
"source": { "oneOf": [{ "type": "null" }, { "type": "object", "required": ["url", "site"], "properties": { "url": { "type": "string" }, "site": { "type": "string" } } }] },
|
|
119
|
+
"image": { "oneOf": [{ "type": "null" }, { "type": "object", "required": ["blob_ref", "sha256", "media_type"], "properties": { "blob_ref": { "$ref": "#/$defs/blobRef" }, "sha256": { "$ref": "#/$defs/sha256" }, "media_type": { "type": "string" } } }] },
|
|
120
|
+
"verdict": { "oneOf": [{ "type": "null" }, { "$ref": "#/$defs/verdict" }] },
|
|
121
|
+
"status": { "enum": ["pending", "ready", "excluded", "unavailable"] },
|
|
122
|
+
"query": { "type": ["string", "null"], "description": "pending 时为真实检索词" }
|
|
123
|
+
}
|
|
124
|
+
},
|
|
125
|
+
"candidate": {
|
|
126
|
+
"type": "object",
|
|
127
|
+
"required": ["item_id", "name", "dimensions_mm", "quote", "ratio_check"],
|
|
128
|
+
"properties": {
|
|
129
|
+
"dimensions_mm": { "description": "页面未给尺寸时为 null,此时比例核对为 unknown", "oneOf": [{ "type": "null" }, { "type": "object", "required": ["width", "height"] }] },
|
|
130
|
+
"quote": { "type": "object", "required": ["page_id", "text"] },
|
|
131
|
+
"ratio_check": { "type": "object", "required": ["status"], "properties": { "status": { "enum": ["consistent", "mismatch", "unknown"] } } }
|
|
132
|
+
}
|
|
133
|
+
},
|
|
134
|
+
"similar": {
|
|
135
|
+
"type": "object",
|
|
136
|
+
"required": ["item_id", "decision", "why"],
|
|
137
|
+
"properties": { "decision": { "enum": ["borrow", "skip"] } }
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
// reference-evidence v1 的唯一字段定义与校验(P2-13.04)。schemas/reference-evidence.v1.json 与本文件的枚举保持一致,
|
|
2
|
+
// 由测试核对;宿主据 schema 与 fixtures 做契约测试,不自行解释字段含义。
|
|
3
|
+
import { createPublicReader } from "../../abelien-harness/scripts/web/public-read.mjs";
|
|
4
|
+
import { createVolcSearch } from "../../abelien-harness/scripts/web/volc.mjs";
|
|
5
|
+
import { redact } from "../../abelien-harness/scripts/web/redact.mjs";
|
|
6
|
+
import { failure, text, record } from "../../abelien-harness/scripts/web/common.mjs";
|
|
7
|
+
|
|
8
|
+
export const SCHEMA = "abelien.reference-evidence.v1";
|
|
9
|
+
export const INTENTS = Object.freeze(["restore", "create", "modify"]);
|
|
10
|
+
export const DEPTHS = Object.freeze(["appearance", "assembly", "functional"]);
|
|
11
|
+
export const PURPOSES = Object.freeze(["identify", "teardown", "similar"]);
|
|
12
|
+
export const ITEM_KINDS = Object.freeze(["user_input", "candidate", "teardown", "spec", "similar"]);
|
|
13
|
+
export const ITEM_STATUSES = Object.freeze(["pending", "ready", "excluded", "unavailable"]);
|
|
14
|
+
export const VERDICT_CODES = Object.freeze(["closest", "ratio_mismatch", "excluded", "borrowed", "not_copied", "fits", "envelope"]);
|
|
15
|
+
export const EVIDENCE_STATUSES = Object.freeze(["collected", "final"]);
|
|
16
|
+
export const KIND_BY_PURPOSE = Object.freeze({ identify: "candidate", teardown: "teardown", similar: "similar" });
|
|
17
|
+
|
|
18
|
+
export const LIMITS = Object.freeze({ queries: 6, count: 5, pages: 12, images: 8, links: 5, textChars: 4000, queryChars: 200,
|
|
19
|
+
pageBytes: 8 * 1024 * 1024, imageBytes: 4 * 1024 * 1024, userImageBytes: 16 * 1024 * 1024, excerptChars: 1200, pageTimeoutMs: 30000, searchTimeoutMs: 30000, concurrency: 4 });
|
|
20
|
+
|
|
21
|
+
export const CODES = Object.freeze({ transport: "REFERENCE_WEB_TRANSPORT", dns: "REFERENCE_WEB_DNS", access: "REFERENCE_WEB_URL", redirect: "REFERENCE_WEB_PAGE",
|
|
22
|
+
timeout: "REFERENCE_WEB_TIMEOUT", url: "REFERENCE_WEB_URL", config: "REFERENCE_WEB_CONFIG", readTimeout: "REFERENCE_PUBLIC_READ_TIMEOUT",
|
|
23
|
+
capability: "REFERENCE_WEB_CAPABILITY", provider: "REFERENCE_WEB_PROVIDER", auth: "REFERENCE_WEB_AUTH", rateLimit: "REFERENCE_WEB_RATE_LIMIT", response: "REFERENCE_WEB_RESPONSE" });
|
|
24
|
+
|
|
25
|
+
export const reader = createPublicReader({ codes: CODES, userAgent: "Abelien-Reference-Evidence/1", accept: "text/html,text/plain,application/xhtml+xml", decodeCharset: true });
|
|
26
|
+
export const imageReader = createPublicReader({ codes: CODES, userAgent: "Abelien-Reference-Evidence/1", accept: "image/png,image/jpeg,image/webp" });
|
|
27
|
+
export const volc = createVolcSearch({ codes: CODES, ownsError: error => Boolean(error?.code?.startsWith("REFERENCE_WEB_")),
|
|
28
|
+
redact: (raw, env = {}) => redact(raw, [env.WEB_SEARCH_API_KEY].filter(text)), timeoutMessage: "reference network request timed out" });
|
|
29
|
+
|
|
30
|
+
export const invalid = message => { throw failure("VALIDATION", message); };
|
|
31
|
+
export const stringList = (value, label, max = 20) => {
|
|
32
|
+
if (!Array.isArray(value) || value.length > max || value.some(item => !text(item) || item.length > 500)) invalid(`${label} must be a list of up to ${max} non-empty strings`);
|
|
33
|
+
return value.map(item => item.trim());
|
|
34
|
+
};
|
|
35
|
+
export const site = url => { try { return new URL(url).hostname; } catch { return null; } };
|
|
36
|
+
|
|
37
|
+
function checkItem(item, index) {
|
|
38
|
+
const at = `items[${index}]`;
|
|
39
|
+
if (!record(item) || !text(item.id) || !ITEM_KINDS.includes(item.kind) || !ITEM_STATUSES.includes(item.status) || typeof item.title !== "string" || typeof item.role !== "string") invalid(`${at} has invalid id, kind, status, title or role`);
|
|
40
|
+
if (item.source !== null && !(record(item.source) && text(item.source.url) && typeof item.source.site === "string")) invalid(`${at}.source must be null or {url, site}`);
|
|
41
|
+
if (item.image !== null && !(record(item.image) && /^sha256:[0-9a-f]{64}$/.test(item.image.blob_ref ?? "") && /^[0-9a-f]{64}$/.test(item.image.sha256 ?? "") && text(item.image.media_type))) invalid(`${at}.image must be null or {blob_ref, sha256, media_type}`);
|
|
42
|
+
if (item.verdict !== null && !(record(item.verdict) && VERDICT_CODES.includes(item.verdict.code) && text(item.verdict.basis))) invalid(`${at}.verdict must be null or {code, basis}`);
|
|
43
|
+
if (item.query !== null && typeof item.query !== "string") invalid(`${at}.query must be null or a string`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** Strict structural check of a reference-evidence v1 body; returns the body. */
|
|
47
|
+
export function validateEvidence(body) {
|
|
48
|
+
if (!record(body) || body.schema !== SCHEMA || !text(body.id) || !EVIDENCE_STATUSES.includes(body.status)) invalid("not a reference-evidence v1 body");
|
|
49
|
+
if (!INTENTS.includes(body.intent) || (body.depth !== null && !DEPTHS.includes(body.depth))) invalid("intent or depth is invalid");
|
|
50
|
+
stringList(body.keep, "keep"); stringList(body.changeable, "changeable"); stringList(body.unknowns, "unknowns", 200);
|
|
51
|
+
for (const name of ["searches", "pages", "images", "items"]) if (!Array.isArray(body[name])) invalid(`${name} must be an array`);
|
|
52
|
+
body.items.forEach(checkItem);
|
|
53
|
+
const ids = body.items.map(item => item.id); if (new Set(ids).size !== ids.length) invalid("item ids must be unique");
|
|
54
|
+
if (body.status === "final") {
|
|
55
|
+
if (!record(body.identity) && body.identity !== null) invalid("identity must be null or an object");
|
|
56
|
+
if (!Array.isArray(body.similar)) invalid("similar must be an array");
|
|
57
|
+
if (!text(body.collected_id)) invalid("final evidence must name its collected_id");
|
|
58
|
+
}
|
|
59
|
+
return body;
|
|
60
|
+
}
|