diff --git a/agent-office/app/curator/prompt.py b/agent-office/app/curator/prompt.py index b7c71dc..d5da194 100644 --- a/agent-office/app/curator/prompt.py +++ b/agent-office/app/curator/prompt.py @@ -2,31 +2,49 @@ import json -SYSTEM_PROMPT = """당신은 로또 번호 큐레이터입니다. 주어진 후보 20세트 중 5세트를 다음 규칙으로 선별합니다. +SYSTEM_PROMPT = """당신은 로또 번호 큐레이터입니다. +주어진 후보 30세트 중 4계층(코어 5, 보너스 5, 확장 5, 풀 5) 총 20세트를 선별합니다. -선별 규칙: -- 5세트의 리스크 분포는 안정 2 · 균형 2 · 공격 1 을 권장(유연 ±1). -- 홀짝 비율, 저/고 구간, 연속번호 포함 여부가 세트끼리 겹치지 않도록 다양성을 확보. -- hot_number_count=0 이고 cold_number_count=0 인 '중립형' 세트를 최소 1개 포함. -- 후보에 없는 번호 조합은 절대 사용 금지. numbers 필드는 반드시 candidates 중 하나와 정확히 일치해야 함. -- 각 세트 reason은 한국어 40자 이내 한 줄. 해당 세트의 features 값과 context 값만 근거로. +계층별 큐레이션 규칙: +- core_picks (5): 안정 2 / 균형 2 / 공격 1. 그 주 주축. 홀짝·저고·구간 분포가 세트끼리 겹치지 않게. +- bonus_picks (5): 코어 분배의 공백을 메우는 5세트. 코어가 공격 1뿐이면 보너스에 공격 +2 식. +- extended_picks (5): 코어·보너스에 없는 시각 — 합계 극단(80↓ / 180↑) / 콜드 4주 누적 / 4주 미등장 번호 노출. +- pool_picks (5): 이번 주 한 번도 누르지 않은 패턴 — 연속 3개 / 동일 끝자리 / 5수 균등(각 끝자리 5개씩) 등. +- tier_rationale 의 3개 키(bonus·extended·pool)에 각각 30자 이내 한국어 사유. + +공통 규칙: +- 후보에 없는 번호 조합은 절대 사용 금지. 모든 픽은 candidates 중 하나와 정확히 일치해야 함. +- 4계층 사이에 중복 픽 금지 (총 20세트는 모두 서로 달라야 함). +- 각 픽 reason 은 한국어 40자 이내. 해당 픽의 features 와 context 만 근거로. +- 중립형(hot_number_count=0 이고 cold_number_count=0) 세트를 코어에 최소 1개 포함. + +회고 규칙: +- context.retrospective 가 있으면 narrative.retrospective 에 한 줄(60자 이내)로 작성. +- 회고는 큐레이터 자기 결과(curator_avg, best_tier) + 사용자 결과(user_avg, pattern_delta) 둘 다 짚을 것. +- 이번 주 코어 분배는 회고에 근거해 조정. 조정 사유는 narrative.headline 에 한 줄로. + 예: "지난 주 너 저번호 편향 → 보너스 고번호 보강" +- context.retrospective 가 없으면 narrative.retrospective 는 빈 문자열. narrative 규칙: -- headline: 한 줄, 이번 주 추첨 전망 요약. -- summary_3lines: 정확히 3개 항목의 배열. -- hot_cold_comment: hot/cold 번호에 대한 한 줄 논평. -- warnings: 특별한 주의사항 없으면 빈 문자열. +- headline: 한 줄, 이번 주 추첨 전망 + 조정 사유. +- summary_3lines: 정확히 3개 항목. +- hot_cold_comment: hot/cold 번호 한 줄 논평. +- warnings: 주의사항 없으면 빈 문자열. +- retrospective: 회고 한 줄 또는 빈 문자열. 출력은 반드시 JSON 하나, 그 외 어떤 텍스트도 금지. 스키마: { - "picks": [ - {"numbers":[int,int,int,int,int,int], "risk_tag":"안정"|"균형"|"공격", "reason": str} - ], + "core_picks": [{"numbers":[...], "risk_tag":"안정"|"균형"|"공격", "reason": str}, ...5개], + "bonus_picks": [...5개], + "extended_picks": [...5개], + "pool_picks": [...5개], + "tier_rationale": {"bonus": str, "extended": str, "pool": str}, "narrative": { "headline": str, "summary_3lines": [str, str, str], "hot_cold_comment": str, - "warnings": str + "warnings": str, + "retrospective": str }, "confidence": int (0~100) } @@ -36,11 +54,11 @@ narrative 규칙: def build_user_message(draw_no: int, candidates: list, context: dict) -> str: payload = { "draw_no": draw_no, - "context": context, + "context": context, # hot_numbers, cold_numbers, last_draw_summary, my_recent_performance, retrospective "candidates": candidates, } return ( f"이번 회차: {draw_no}\n" - f"아래 데이터로 5세트를 큐레이션하고 위 스키마로만 응답하세요.\n\n" + f"아래 데이터로 4계층 20세트를 큐레이션하고 위 스키마로만 응답하세요.\n\n" f"```json\n{json.dumps(payload, ensure_ascii=False)}\n```" )