fix(bom): compact sku synthesis into tuples
This commit is contained in:
parent
3c8d7f0801
commit
4f1571d508
@ -11,7 +11,7 @@ describe('BOM 多模态网关', () => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
it('结构汇总默认请求一次性 JSON,并读取标准非流式响应', async () => {
|
||||
it('供应商忽略流式偏好返回标准 JSON 时仍能读取结果', async () => {
|
||||
const fetchSpy = jest.spyOn(global, 'fetch').mockResolvedValue(
|
||||
new Response(JSON.stringify({
|
||||
id: 'call-json-response',
|
||||
@ -43,7 +43,7 @@ describe('BOM 多模态网关', () => {
|
||||
const requestBody = JSON.parse(String(fetchSpy.mock.calls[0]?.[1]?.body)) as {
|
||||
stream?: boolean
|
||||
}
|
||||
expect(requestBody.stream).toBe(false)
|
||||
expect(requestBody.stream).toBe(true)
|
||||
expect(result).toMatchObject({
|
||||
id: 'call-json-response',
|
||||
model: 'gpt-5.6-terra',
|
||||
|
||||
@ -622,12 +622,12 @@ export async function callBomCostingMultimodalModel(input: {
|
||||
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
||||
let response: Response
|
||||
// The UI streams durable stage events, not raw model tokens. Synthesis only
|
||||
// needs one strict JSON object, so asking the provider for a regular JSON
|
||||
// response avoids OpenAI-compatible gateways that emit a partial SSE body
|
||||
// and then keep the socket open for minutes. Image analysis keeps streaming
|
||||
// because those responses can be substantially larger.
|
||||
const requestStreamingResponse = input.operation === 'IMAGE_FACTS'
|
||||
// Production verification shows this provider starts and completes small
|
||||
// streamed JSON responses reliably, while its buffered response mode may
|
||||
// not return headers before the deadline. Keep SSE as the requested mode;
|
||||
// the content-type branch below still accepts a regular JSON response from
|
||||
// providers that ignore this preference.
|
||||
const requestStreamingResponse = true
|
||||
const timeoutSignal = AbortSignal.timeout(input.timeoutMs)
|
||||
const requestSignal = input.signal
|
||||
? AbortSignal.any([timeoutSignal, input.signal])
|
||||
|
||||
@ -119,7 +119,7 @@ describe('BOM v6 结构蓝图与 SKU 展开', () => {
|
||||
const stages: BomCostingStructureStageProgress[] = []
|
||||
const checkpoints: BomCostingStructureCheckpoint[] = []
|
||||
const instantiationRequests: Array<{
|
||||
skuTruth: Array<{ externalSkuId: string }>
|
||||
skuTruth: Array<{ id: string }>
|
||||
structureGroup: { skuIds: string[] }
|
||||
facts: Array<{ factId: string; relatedSkuIds: string[] }>
|
||||
}> = []
|
||||
@ -129,7 +129,7 @@ describe('BOM v6 结构蓝图与 SKU 展开', () => {
|
||||
}
|
||||
const userPayload = JSON.parse(body.messages[1]?.content ?? '{}') as {
|
||||
outputContractVersion?: string
|
||||
skuTruth?: Array<{ externalSkuId: string; name: string }>
|
||||
skuTruth?: Array<{ id: string; name: string }>
|
||||
structureGroup?: { skuIds: string[] }
|
||||
facts?: Array<{ factId: string; relatedSkuIds: string[] }>
|
||||
}
|
||||
@ -158,34 +158,23 @@ describe('BOM v6 结构蓝图与 SKU 展开', () => {
|
||||
return streamingResponse(JSON.stringify({
|
||||
skus: skuTruth.map((sku) => {
|
||||
const widthMm = Number(sku.name.match(/(\d+)cm/)?.[1] ?? 140) * 10
|
||||
const inferredEvidence = [{
|
||||
basis: 'STRUCTURAL_INFERENCE',
|
||||
note: '依据柜体高和深度推导',
|
||||
factIds: [],
|
||||
derivation: '侧板高度=整机高度;侧板宽度=柜体深度',
|
||||
reviewReason: '未发现侧板单独尺寸标注',
|
||||
confidence: 0.5,
|
||||
}]
|
||||
return {
|
||||
externalSkuId: sku.externalSkuId,
|
||||
dimensions: { lengthMm: widthMm, widthMm: 620, heightMm: 2200 },
|
||||
bomLines: componentSlots.map((slot) => ({
|
||||
externalLineId: slot.componentKey,
|
||||
materialName: slot.category === 'BOARD'
|
||||
sku: sku.id,
|
||||
d: [widthMm, 620, 2200],
|
||||
lines: componentSlots.map((slot) => ([
|
||||
slot.componentKey,
|
||||
slot.componentKey === 'enclosure-side' ? 2 : 1,
|
||||
slot.category === 'BOARD' ? '块' : '套',
|
||||
slot.category === 'BOARD' ? 2200 : null,
|
||||
slot.category === 'BOARD' ? 620 : null,
|
||||
slot.category === 'BOARD' ? 19.95 : null,
|
||||
slot.category === 'BOARD'
|
||||
? '橡木'
|
||||
: slot.category === 'PACKAGING'
|
||||
? '瓦楞纸箱'
|
||||
: undefined,
|
||||
lengthMm: slot.category === 'BOARD' ? 2200 : undefined,
|
||||
widthMm: slot.category === 'BOARD' ? 620 : undefined,
|
||||
heightMm: slot.category === 'BOARD' ? 19.95 : undefined,
|
||||
quantity: slot.componentKey === 'enclosure-side' ? 2 : 1,
|
||||
unit: slot.category === 'BOARD' ? '块' : '套',
|
||||
evidenceFactIds: ['fact-common'],
|
||||
derivation: inferredEvidence[0]?.derivation,
|
||||
reviewReason: inferredEvidence[0]?.reviewReason,
|
||||
confidence: 0.5,
|
||||
})),
|
||||
: null,
|
||||
['fact-common'],
|
||||
])),
|
||||
}
|
||||
}),
|
||||
}))
|
||||
@ -217,6 +206,23 @@ describe('BOM v6 结构蓝图与 SKU 展开', () => {
|
||||
confidence: 0.9,
|
||||
}],
|
||||
},
|
||||
...Array.from({ length: 30 }, (_, index) => ({
|
||||
imageId: `noise-image-${index + 1}`,
|
||||
sourceImageUrl: `https://img.example.com/noise-${index + 1}.jpg`,
|
||||
kind: 'DETAIL' as const,
|
||||
label: `低优先级说明 ${index + 1}`,
|
||||
externalSkuIds: [],
|
||||
observedTexts: [],
|
||||
facts: [{
|
||||
factId: `fact-noise-${index + 1}`,
|
||||
kind: 'TEXT' as const,
|
||||
subject: '营销说明',
|
||||
value: `说明 ${index + 1}`,
|
||||
relatedSkuIds: [],
|
||||
basis: 'SOURCE_TEXT' as const,
|
||||
confidence: 0.4,
|
||||
}],
|
||||
})),
|
||||
...snapshot.skus.map((sku) => ({
|
||||
imageId: `image-${sku.externalSkuId}`,
|
||||
sourceImageUrl: `https://img.example.com/${sku.externalSkuId}.jpg`,
|
||||
@ -259,7 +265,8 @@ describe('BOM v6 结构蓝图与 SKU 展开', () => {
|
||||
expect(instantiationRequests).toHaveLength(7)
|
||||
for (const request of instantiationRequests) {
|
||||
expect(request.skuTruth).toHaveLength(1)
|
||||
const skuId = request.skuTruth[0]?.externalSkuId
|
||||
expect(request.facts).toHaveLength(24)
|
||||
const skuId = request.skuTruth[0]?.id
|
||||
expect(request.structureGroup.skuIds).toEqual([skuId])
|
||||
expect(request.facts.map((fact) => fact.factId)).toEqual(expect.arrayContaining([
|
||||
'fact-common',
|
||||
@ -370,7 +377,7 @@ describe('BOM v6 结构蓝图与 SKU 展开', () => {
|
||||
: content.find((item) => item.type === 'text')?.text ?? '{}'
|
||||
const userPayload = JSON.parse(userText) as {
|
||||
outputContractVersion?: string
|
||||
skuTruth?: Array<{ externalSkuId: string }>
|
||||
skuTruth?: Array<{ id: string }>
|
||||
}
|
||||
if (userPayload.outputContractVersion === 'structure-blueprint/review-v1') {
|
||||
return streamingResponse(JSON.stringify({
|
||||
@ -415,27 +422,18 @@ describe('BOM v6 结构蓝图与 SKU 展开', () => {
|
||||
}
|
||||
return streamingResponse(JSON.stringify({
|
||||
skus: (userPayload.skuTruth ?? []).map((sku) => ({
|
||||
externalSkuId: sku.externalSkuId,
|
||||
dimensions: { lengthMm: 1_400, widthMm: 620, heightMm: 2_200 },
|
||||
bomLines: componentSlots.map((slot) => ({
|
||||
externalLineId: slot.componentKey,
|
||||
category: slot.category,
|
||||
partName: slot.partName,
|
||||
materialName: slot.category === 'BOARD' ? '橡木' : '',
|
||||
lengthMm: slot.category === 'BOARD' ? 2_200 : undefined,
|
||||
widthMm: slot.category === 'BOARD' ? 620 : undefined,
|
||||
heightMm: slot.category === 'BOARD' ? 18 : undefined,
|
||||
quantity: slot.componentKey === 'enclosure-side' ? 2 : 1,
|
||||
unit: slot.category === 'BOARD' ? '块' : '套',
|
||||
evidence: [{
|
||||
basis: 'STRUCTURAL_INFERENCE',
|
||||
factIds: ['fact-1'],
|
||||
derivation: slot.dimensionRule,
|
||||
reviewReason: '板厚未解决',
|
||||
confidence: 0.3,
|
||||
}],
|
||||
confidence: 0.3,
|
||||
})),
|
||||
sku: sku.id,
|
||||
d: [1_400, 620, 2_200],
|
||||
lines: componentSlots.map((slot) => ([
|
||||
slot.componentKey,
|
||||
slot.componentKey === 'enclosure-side' ? 2 : 1,
|
||||
slot.category === 'BOARD' ? '块' : '套',
|
||||
slot.category === 'BOARD' ? 2_200 : null,
|
||||
slot.category === 'BOARD' ? 620 : null,
|
||||
slot.category === 'BOARD' ? 18 : null,
|
||||
slot.category === 'BOARD' ? '橡木' : null,
|
||||
['fact-1'],
|
||||
])),
|
||||
})),
|
||||
}))
|
||||
})
|
||||
|
||||
@ -36,6 +36,7 @@ const MAX_BLUEPRINT_COMPONENT_SLOTS = 48
|
||||
const MAX_BLUEPRINT_RELATIONS = 80
|
||||
const MAX_BLUEPRINT_QUESTIONS = 16
|
||||
const MAX_SKUS_PER_INSTANTIATION_CALL = 1
|
||||
const MAX_INSTANTIATION_FACTS = 24
|
||||
|
||||
const BLUEPRINT_SYSTEM_PROMPT = `你是家具产品结构工程师。系统已经根据商品事实和版本化结构知识建立 baselineBlueprint。你只审查并返回必要修正,不输出价格,不输出每个 SKU 的最终 BOM,也不要原样抄回 baselineBlueprint。
|
||||
|
||||
@ -59,11 +60,11 @@ const INSTANTIATION_SYSTEM_PROMPT = `你是家具 BOM 结构工程师。根据
|
||||
|
||||
硬规则:
|
||||
1. 只输出一个 JSON 对象,不要 Markdown 或解释。
|
||||
2. externalSkuId 必须完整覆盖本批 skuTruth,每个 SKU 一次;不得组合或补造 SKU。
|
||||
3. 每个 required componentSlot 都必须生成一条 bomLine,externalLineId 必须等于 componentKey。同组兄弟 SKU 使用相同核心 componentKey。category、partName 和完整 evidence 由系统从蓝图补齐,不要回写这些重复字段。
|
||||
2. skus[].sku 必须完整覆盖本批 skuTruth 中的 id,每个 SKU 一次;不得组合或补造 SKU。
|
||||
3. 每个 required componentSlot 都必须生成一条 lines 元组,元组第 1 项必须等于 componentKey。同组兄弟 SKU 使用相同核心 componentKey。部件名称、分类、推导说明和可信度由系统补齐,不要重复输出。
|
||||
4. 每条 SKU 的尺寸、数量、材质和套餐组成独立计算。颜色不改变结构时不能让基础部件消失。
|
||||
5. BOARD 的 lengthMm/widthMm 是单块板件尺寸,heightMm 只能是板厚。明确板厚只应用于证据指定部件;目测厚度 confidence 不高于 0.45。
|
||||
6. 每行只返回 externalLineId、quantity、unit、材料/工艺、尺寸、evidenceFactIds、derivation、reviewReason 和 confidence。由整机尺寸推导板件时,derivation 写清公式或装配关系,reviewReason 写明未知项。
|
||||
5. BOARD 的长/宽是单块板件尺寸,厚只能是板厚。明确板厚只应用于证据指定部件;未知数值必须用 null,不得编造。
|
||||
6. d 固定为 [整机长,整机宽或深,整机高];每条 lines 固定为 [componentKey,数量,单位,长mm或null,宽mm或null,厚mm或null,材质或工艺或null,[直接证据factId]]。除该固定元组外不要输出任何行字段。
|
||||
7. 公司资料库是后续唯一正式价格来源;不得输出价格、售价倒推或损耗系数。`
|
||||
|
||||
const blueprintProductFamilySchema = z.object({
|
||||
@ -149,42 +150,66 @@ const verificationPayloadSchema = z.object({
|
||||
|
||||
const compactInstantiatedBomLineSchema = z.object({
|
||||
externalLineId: z.string().trim().min(1).max(120),
|
||||
materialCode: z.string().trim().max(120).optional(),
|
||||
materialName: z.string().trim().max(200).optional(),
|
||||
materialSpec: z.string().trim().max(200).optional(),
|
||||
processName: z.string().trim().max(200).optional(),
|
||||
packagingKind: z.string().trim().max(120).optional(),
|
||||
descriptor: z.string().trim().max(200).optional(),
|
||||
lengthMm: z.number().finite().positive().optional(),
|
||||
widthMm: z.number().finite().positive().optional(),
|
||||
heightMm: z.number().finite().positive().optional(),
|
||||
quantity: z.number().finite().positive(),
|
||||
unit: z.string().trim().min(1).max(40),
|
||||
weightKg: z.number().finite().positive().optional(),
|
||||
evidenceFactIds: z.array(z.string().trim().min(1).max(160)).max(30).default([]),
|
||||
derivation: z.string().trim().max(800).default(''),
|
||||
reviewReason: z.string().trim().max(500).default(''),
|
||||
confidence: z.number().finite().min(0).max(1),
|
||||
})
|
||||
|
||||
const instantiatedSkuSchema = z.object({
|
||||
externalSkuId: z.string().trim().min(1).max(160),
|
||||
dimensions: z.object({
|
||||
lengthMm: z.number().finite().positive().optional(),
|
||||
widthMm: z.number().finite().positive().optional(),
|
||||
heightMm: z.number().finite().positive().optional(),
|
||||
}).default({}),
|
||||
weightKg: z.number().finite().positive().optional(),
|
||||
packaging: z.object({
|
||||
lengthMm: z.number().finite().positive().optional(),
|
||||
widthMm: z.number().finite().positive().optional(),
|
||||
heightMm: z.number().finite().positive().optional(),
|
||||
weightKg: z.number().finite().positive().optional(),
|
||||
}).optional(),
|
||||
bomLines: z.array(compactInstantiatedBomLineSchema).min(1).max(80),
|
||||
})
|
||||
const nullablePositiveNumberSchema = z.number().finite().positive().nullable()
|
||||
|
||||
const compactInstantiatedBomLineTupleSchema = z.tuple([
|
||||
z.string().trim().min(1).max(120),
|
||||
z.number().finite().positive(),
|
||||
z.string().trim().min(1).max(40),
|
||||
nullablePositiveNumberSchema,
|
||||
nullablePositiveNumberSchema,
|
||||
nullablePositiveNumberSchema,
|
||||
z.string().trim().max(200).nullable(),
|
||||
z.array(z.string().trim().min(1).max(160)).max(30),
|
||||
]).transform(([
|
||||
externalLineId,
|
||||
quantity,
|
||||
unit,
|
||||
lengthMm,
|
||||
widthMm,
|
||||
heightMm,
|
||||
descriptor,
|
||||
evidenceFactIds,
|
||||
]) => compactInstantiatedBomLineSchema.parse({
|
||||
externalLineId,
|
||||
quantity,
|
||||
unit,
|
||||
lengthMm: lengthMm ?? undefined,
|
||||
widthMm: widthMm ?? undefined,
|
||||
heightMm: heightMm ?? undefined,
|
||||
descriptor: descriptor || undefined,
|
||||
evidenceFactIds,
|
||||
}))
|
||||
|
||||
const instantiatedSkuTupleSchema = z.object({
|
||||
sku: z.string().trim().min(1).max(160),
|
||||
d: z.tuple([
|
||||
nullablePositiveNumberSchema,
|
||||
nullablePositiveNumberSchema,
|
||||
nullablePositiveNumberSchema,
|
||||
]),
|
||||
lines: z.array(compactInstantiatedBomLineTupleSchema).min(1).max(80),
|
||||
}).transform(({ sku, d, lines }) => ({
|
||||
externalSkuId: sku,
|
||||
dimensions: {
|
||||
lengthMm: d[0] ?? undefined,
|
||||
widthMm: d[1] ?? undefined,
|
||||
heightMm: d[2] ?? undefined,
|
||||
},
|
||||
bomLines: lines,
|
||||
}))
|
||||
|
||||
const instantiationPayloadSchema = z.object({
|
||||
skus: z.array(instantiatedSkuSchema).min(1).max(MAX_SKUS_PER_INSTANTIATION_CALL),
|
||||
skus: z.array(instantiatedSkuTupleSchema).min(1).max(MAX_SKUS_PER_INSTANTIATION_CALL),
|
||||
})
|
||||
|
||||
const BOM_COSTING_STRUCTURE_CHECKPOINT_KIND =
|
||||
@ -691,9 +716,45 @@ function groupFacts(
|
||||
skuIds: string[],
|
||||
) {
|
||||
const skuSet = new Set(skuIds)
|
||||
return compactFacts(factSnapshot, MAX_BLUEPRINT_FACTS).filter((fact) => (
|
||||
fact.relatedSkuIds.length === 0 || fact.relatedSkuIds.some((skuId) => skuSet.has(skuId))
|
||||
))
|
||||
const kindPriority = new Map([
|
||||
['DIMENSION', 10],
|
||||
['MATERIAL', 9],
|
||||
['PART', 8],
|
||||
['STRUCTURE', 8],
|
||||
['MECHANISM', 7],
|
||||
['HARDWARE', 6],
|
||||
['PACKAGING', 5],
|
||||
['PROCESS', 5],
|
||||
['WEIGHT', 4],
|
||||
['SKU_DIFFERENCE', 4],
|
||||
['TEXT', 1],
|
||||
])
|
||||
return compactFacts(factSnapshot, 500)
|
||||
.filter((fact) => (
|
||||
fact.relatedSkuIds.length === 0 ||
|
||||
fact.relatedSkuIds.some((skuId) => skuSet.has(skuId))
|
||||
))
|
||||
.sort((left, right) => {
|
||||
const leftSpecific = left.relatedSkuIds.some((skuId) => skuSet.has(skuId)) ? 1 : 0
|
||||
const rightSpecific = right.relatedSkuIds.some((skuId) => skuSet.has(skuId)) ? 1 : 0
|
||||
if (leftSpecific !== rightSpecific) return rightSpecific - leftSpecific
|
||||
const kindDifference = (kindPriority.get(right.kind) ?? 0) -
|
||||
(kindPriority.get(left.kind) ?? 0)
|
||||
if (kindDifference !== 0) return kindDifference
|
||||
return right.confidence - left.confidence
|
||||
})
|
||||
.slice(0, MAX_INSTANTIATION_FACTS)
|
||||
.map((fact) => ({
|
||||
factId: fact.factId,
|
||||
relatedSkuIds: fact.relatedSkuIds,
|
||||
kind: fact.kind,
|
||||
subject: fact.subject,
|
||||
value: fact.value,
|
||||
numericValue: fact.numericValue,
|
||||
unit: fact.unit,
|
||||
basis: fact.basis,
|
||||
confidence: fact.confidence,
|
||||
}))
|
||||
}
|
||||
|
||||
function hydrateInstantiationLines(input: {
|
||||
@ -728,30 +789,39 @@ function hydrateInstantiationLines(input: {
|
||||
? Math.max(...factRecords.map(({ fact }) => fact.confidence))
|
||||
: slot.confidence
|
||||
const confidence = basis === 'STRUCTURAL_INFERENCE'
|
||||
? Math.min(0.55, line.confidence, slot.confidence)
|
||||
: Math.min(1, line.confidence, evidenceConfidence)
|
||||
? Math.min(0.55, slot.confidence)
|
||||
: Math.min(1, slot.confidence, evidenceConfidence)
|
||||
const imageUrl = factRecords[0]?.imageUrl
|
||||
const derivation = line.derivation || [
|
||||
const derivation = [
|
||||
slot.quantityRule,
|
||||
slot.dimensionRule,
|
||||
].filter(Boolean).join(';')
|
||||
const reviewReason = line.reviewReason || slot.reviewFlags.join(';')
|
||||
const missingBoardDimensions = slot.category === 'BOARD' && (
|
||||
line.lengthMm === undefined ||
|
||||
line.widthMm === undefined ||
|
||||
line.heightMm === undefined
|
||||
)
|
||||
const reviewReason = [
|
||||
...slot.reviewFlags,
|
||||
factRecords.length === 0 ? '当前行依据共享结构规则推导' : null,
|
||||
missingBoardDimensions ? '板件尺寸或板厚仍需复核' : null,
|
||||
].filter(Boolean).join(';')
|
||||
const descriptorFields = slot.category === 'PROCESSING'
|
||||
? { processName: line.descriptor }
|
||||
: slot.category === 'PACKAGING'
|
||||
? { packagingKind: line.descriptor }
|
||||
: { materialName: line.descriptor }
|
||||
|
||||
return competitorBomLineSchema.parse({
|
||||
externalLineId: line.externalLineId,
|
||||
category: slot.category,
|
||||
partName: slot.partName,
|
||||
materialCode: line.materialCode,
|
||||
materialName: line.materialName,
|
||||
materialSpec: line.materialSpec,
|
||||
processName: line.processName,
|
||||
packagingKind: line.packagingKind,
|
||||
...descriptorFields,
|
||||
lengthMm: line.lengthMm,
|
||||
widthMm: line.widthMm,
|
||||
heightMm: line.heightMm,
|
||||
quantity: line.quantity,
|
||||
unit: line.unit,
|
||||
weightKg: line.weightKg,
|
||||
evidence: [{
|
||||
basis,
|
||||
imageUrl,
|
||||
@ -890,24 +960,16 @@ async function instantiateBlueprint(input: {
|
||||
systemPrompt: INSTANTIATION_SYSTEM_PROMPT,
|
||||
userContent: JSON.stringify({
|
||||
task: '按结构蓝图为本批每个 SKU 独立生成 BOM。',
|
||||
outputContractVersion: 'structure-instantiation/compact-v1',
|
||||
outputContractVersion: 'structure-instantiation/tuple-v1',
|
||||
outputShape: {
|
||||
skus: [{
|
||||
externalSkuId: '真实 SKU ID',
|
||||
dimensions: '当前 SKU 整机尺寸,可缺省未知项',
|
||||
bomLines: [{
|
||||
externalLineId: '必须等于 componentKey',
|
||||
quantity: '数量',
|
||||
unit: '单位',
|
||||
materialName: '材料名,可选',
|
||||
lengthMm: '单件长度,可选',
|
||||
widthMm: '单件宽度,可选',
|
||||
heightMm: '板件厚度或单件高度,可选',
|
||||
evidenceFactIds: '只引用输入 factId',
|
||||
derivation: '简短公式或装配关系',
|
||||
reviewReason: '未知项,可为空',
|
||||
confidence: '0 到 1',
|
||||
}],
|
||||
sku: '真实 SKU ID',
|
||||
d: ['整机长mm或null', '整机宽/深mm或null', '整机高mm或null'],
|
||||
lines: [[
|
||||
'componentKey', '数量', '单位',
|
||||
'长mm或null', '宽mm或null', '厚mm或null',
|
||||
'材质/工艺或null', ['直接证据factId'],
|
||||
]],
|
||||
}],
|
||||
},
|
||||
contractRetry: attempt > 1,
|
||||
@ -926,14 +988,19 @@ async function instantiateBlueprint(input: {
|
||||
confidence: slot.confidence,
|
||||
})),
|
||||
},
|
||||
skuTruth: skuBatch.map(serializeSku),
|
||||
skuTruth: skuBatch.map((sku) => ({
|
||||
id: sku.externalSkuId,
|
||||
name: sku.name,
|
||||
size: sku.size,
|
||||
attributes: sku.attributes,
|
||||
})),
|
||||
facts: groupFacts(
|
||||
input.factSnapshot,
|
||||
skuBatch.map((sku) => sku.externalSkuId),
|
||||
),
|
||||
reviewInstruction: input.analysisInstruction?.trim() || undefined,
|
||||
}),
|
||||
maxTokens: 2_200,
|
||||
maxTokens: 1_200,
|
||||
timeoutMs: getTimeoutMs(45_000),
|
||||
retrySynthesisTimeout: false,
|
||||
signal: input.signal,
|
||||
@ -985,8 +1052,6 @@ async function instantiateBlueprint(input: {
|
||||
imageUrl: source.imageUrl ?? undefined,
|
||||
attributes: { ...source.attributes },
|
||||
dimensions: result.dimensions,
|
||||
weightKg: result.weightKg,
|
||||
packaging: result.packaging,
|
||||
bomLines: hydrateInstantiationLines({
|
||||
lines: result.bomLines,
|
||||
group,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user