From 4d6afc10c8a8ce3d221d84179b92d8053b725194 Mon Sep 17 00:00:00 2001 From: kim Date: Wed, 23 Sep 2026 11:03:55 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=9E=91=EC=97=85=EC=8B=A4=20=EC=84=B8?= =?UTF-8?q?=EC=85=98=20=ED=94=84=EB=A1=AC=ED=94=84=ED=8A=B8=EC=97=90=20?= =?UTF-8?q?=ED=99=9C=EC=84=B1=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20BOM?= =?UTF-8?q?(=EB=B6=80=ED=92=88=20=EB=AA=A9=EB=A1=9D)=20=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 2026-09-23 실측: ws_proj 세션에서 "608ZZ 베어링 1개만 있으면 되나?"에 모델이 ①도구 호출 없이 "조회하겠습니다"만 말고 턴 종료(BOM이 프롬프트에 없어 도구를 써야 하는데 안 씀) → 사용자 "??" ②다음 턴엔 도구 0건으로 "확인해 보니 608ZZ가 등록 안 됐다"고 지어냄 — 실제 BOM엔 608ZZ ×4(J2/J3 감속기 출력축 지지용)가 등록돼 있어 정반대의 사실이었다. workshopProjectCtx에 부품 목록(이름·수량·단가·상태·메모)을 턴마다 새로 읽어 주입한다. 목록이 프롬프트에 있으면 부품 질문에 도구 왕복 없이 정확히 답한다. Co-Authored-By: Claude Code --- src/gateway/chat/handle-chat.ts | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/src/gateway/chat/handle-chat.ts b/src/gateway/chat/handle-chat.ts index b5d1755..25174eb 100644 --- a/src/gateway/chat/handle-chat.ts +++ b/src/gateway/chat/handle-chat.ts @@ -300,10 +300,36 @@ async function handleChat( `바로 이 경로로 scad_to_stl을 실행할 것.`; } } catch { /* noop — 스캔 실패해도 나머지 컨텍스트는 유지 */ } + // 부품 목록(BOM)도 프롬프트에 주입한다 — 2026-09-23 실측: BOM이 프롬프트에 없으니 + // 모델이 "조회하겠습니다"만 말고 턴을 끝내고(도구 0건), 다음 턴엔 도구 없이 + // "확인해 보니 608ZZ 베어링이 등록 안 됐다"고 지어냈다. 실제 BOM엔 608ZZ ×4가 + // 등록돼 있었다(정반대). 목록이 프롬프트에 있으면 도구 왕복 없이 정확히 답한다. + let partsBlock = ''; + try { + const parts: any[] = Array.isArray(proj.parts) ? proj.parts : []; + if (parts.length > 0) { + const lines = parts.map((p, i) => { + const qty = Number(p.qty) || 0; + const unit = Number(p.unitPrice) || 0; + const status = String(p.status || '').trim(); + const memo = String(p.memo || '').trim(); + return `${i + 1}. ${p.name} ×${qty}` + + (unit > 0 ? ` (${unit.toLocaleString()}원/개)` : '') + + (status ? ` [${status}]` : '') + + (memo ? ` — ${memo}` : ''); + }); + partsBlock = `\n\n프로젝트 부품 목록(BOM, ${parts.length}개 — 대시보드에 등록된 실제 데이터, 이 목록이 권위다):\n` + + lines.join('\n') + + `\n부품의 등록 여부·수량을 물으면 반드시 이 목록으로 답한다(도구로 재확인하지 않는다). ` + + `목록에 없는 부품은 "아직 등록 안 됨"이 확실하다.`; + } else { + partsBlock = '\n\n프로젝트에 아직 등록된 부품이 없다(빈 BOM).'; + } + } catch { /* noop */ } return `[작업실(workshop) 세션 — 지금 사용자가 보고 있는 프로젝트: "${proj.name}" (id: ${proj.id}). ` + `이 프로젝트의 파일(STL/scad/사진 등)은 워크스페이스 기준 workshop/${proj.id}/ 아래에만 있다. ` + `파일을 찾거나 만들 때 이 프로젝트 폴더만 보고, 다른 프로젝트 폴더(workshop/<다른id>/)는 ` + - `사용자가 명시적으로 그 프로젝트를 언급하지 않는 한 후보로 내놓지 말 것.${scadHint}]`; + `사용자가 명시적으로 그 프로젝트를 언급하지 않는 한 후보로 내놓지 말 것.${scadHint}${partsBlock}]`; } catch { return ''; } })(); const callerContextWithWorkshop = workshopProjectCtx