From 0d47e989bafabfec1bc70249d272c1442b342268 Mon Sep 17 00:00:00 2001 From: ttipo Date: Sun, 21 Jun 2026 12:32:35 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20AI=20=EC=B6=94=EC=B2=9C=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=EB=8B=B9=20=ED=95=AD=EB=AA=A9=20?= =?UTF-8?q?=EC=88=98=20=EC=A0=9C=ED=95=9C=20=ED=95=B4=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 프롬프트의 '3~6개씩' 제한 제거 → 카테고리별로 필요한 만큼 제안 - parseSuggestions 의 카테고리당 15개 슬라이스 캡 제거(응답 길이는 max_tokens 가 자연 제한) - 항목이 늘어 잘리지 않도록 suggest max_tokens 3072 → 4096 런타임 재현: 동일 입력에서 필수8·관리6·보안8·부가6(총 28개)로 더 풍부하게 반환. 백엔드 build/lint 0. Co-Authored-By: Claude Opus 4.8 (1M context) --- backend/src/modules/ai/ai.service.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/backend/src/modules/ai/ai.service.ts b/backend/src/modules/ai/ai.service.ts index f7be484..f8c7a86 100644 --- a/backend/src/modules/ai/ai.service.ts +++ b/backend/src/modules/ai/ai.service.ts @@ -84,7 +84,7 @@ const SUGGEST_SYSTEM_PROMPT = [ '[규칙]', '- 반드시 아래 JSON 형식으로만 응답하세요. 설명·인사·마크다운 코드펜스 없이 순수 JSON 만 출력합니다.', '- 카테고리는 정확히 "필수 기능", "관리 기능", "보안 기능", "부가 기능" 4개를 이 순서대로 사용하세요. (다른 이름·추가/누락 카테고리 금지)', - '- 각 카테고리에 해당하는 항목을 3~6개씩 제안합니다. 적합한 항목이 없으면 빈 배열([])로 두세요.', + '- 각 카테고리에 필요한 항목을 충분히 제안하세요. 항목 수에 제한은 없습니다. 적합한 항목이 없으면 빈 배열([])로 두세요.', '- 각 항목은 한국어로 간결한 한 문장(체크리스트 항목)으로 작성합니다.', '- 업무·프로젝트 맥락에 구체적으로 맞추고 일반론은 피하세요.', '', @@ -203,12 +203,12 @@ export class AiService { content, ].join('\n'); - // 4개 카테고리 × 다수 항목의 구조화 JSON 이라 토큰 여유를 넉넉히 준다 - // (1024 면 응답이 중간에 잘려 JSON 파싱이 실패할 수 있음) + // 4개 카테고리 × 항목 수 무제한의 구조화 JSON 이라 토큰 여유를 넉넉히 준다 + // (부족하면 응답이 중간에 잘려 JSON 파싱이 실패할 수 있음) const raw = await this.callClaude( [{ role: 'user', content: userMsg }], SUGGEST_SYSTEM_PROMPT, - 3072, + 4096, ); return { groups: this.parseSuggestions(raw) }; } @@ -242,11 +242,11 @@ export class AiService { const category = typeof obj.category === 'string' ? obj.category.trim() : ''; if (!category || !Array.isArray(obj.items)) continue; + // 카테고리당 항목 수 제한 없음 — 응답 길이는 max_tokens 가 자연 제한 const items = obj.items .filter((x): x is string => typeof x === 'string') .map((s) => s.trim()) - .filter((s) => s.length > 0) - .slice(0, 15); + .filter((s) => s.length > 0); if (items.length > 0) result.push({ category, items }); } if (result.length === 0) {