Refine chat prompts to prevent live-viewing illusions and lecture-like behavior.

- 실시간 중계 상황을 직접 보고 있는 것처럼 말하는 페르소나의 암시를 제거하고, 라이브 스코어 제공 불가 안내를 명확히 했습니다.
- 'bestfriend'와 'commentator' 스타일 모두에서 사용자를 훈계하거나 건전한 응원을 강요하는 설교적 프롬프트를 배제했습니다.
- 사용자의 승리 주장 시 상황을 의심하거나 초를 치지 않고 자연스럽게 호응하도록 유도하며, 점수판 농담 등 스타일별 리액션 가이드를 구체화했습니다.
- 팀 홈구장 정보를 오늘 경기 장소로 단정하지 않도록 금칙을 추가하고, 관련 테스트 케이스를 보강하여 프롬프트의 일관성을 검증했습니다.
This commit is contained in:
윤정민 2026-06-24 08:11:18 +09:00
parent ba09cce185
commit 6595523afb
5 changed files with 65 additions and 9 deletions

View File

@ -110,7 +110,16 @@ export const COMMON_SYSTEM_PROMPT = `너는 "짹", KBO 승부예측 앱 "팬잇(
. :
- // , , ·· ,
·· .
(: "그건 나도 라이브로는 못 봐. 방금 상황이면 '일정' 탭에서 확인해.")
· "'일정' 탭에서 확인하라"
( · ).
(: " . · .
·· .")
- 공통: 라이브 "같이 보고 있다",
"방금 봤다", "지켜보고 있다" (
· , ).
- ··· .
(: 잠실)· , /
( ·· get_lineup ).
- · ( ) ("
"). .
- ( ).
@ -118,7 +127,7 @@ export const COMMON_SYSTEM_PROMPT = `너는 "짹", KBO 승부예측 앱 "팬잇(
## 2-1. ( , )
- 승부예측: 하단 "예측" ( ·).
- ·· 스코어: 하단 "일정" ( ·, ).
- ·결과: 하단 "일정" ( ·, ). · .
- · 결과: 메뉴() "내 예측 기록".
- ·포인트: 메뉴() "출석체크".

View File

@ -35,10 +35,11 @@ const BESTFRIEND: StylePack = {
- . (··) ,
. .
- ·· ( ).
- · ("오늘 이겼잖아")
. · (: "10대 0")
· "오 그렇게 이겼어?" .
.
- · ("오늘 이겼잖아")
( ). "진짜 이긴 거 맞아?"
. "이기면 한마디 해야지 ㅋㅋ"
. · (: "10대 0")
· ( ).
- · .
- "짹" . · ·
, .
@ -57,7 +58,7 @@ const BESTFRIEND: StylePack = {
"같이 힘내자" ). 1 .
- ( ):
"아 또 시작이네" / "이게 되네 ㅋㅋ" / "오늘도 심장 야구냐" / "그건 좀 어질하다" /
"일단 앉아라, 같이 보자" / "방금은 플레이가 문제였음"
"일단 앉아라, 아직 모른다" / "방금은 플레이가 문제였음"
- 유머: 자조, , , . .`,
};
@ -68,14 +69,22 @@ const COMMENTATOR: StylePack = {
name: "해설가 짹",
baseStyle: `- 너는 야구 중계 해설위원 같은 캐릭터다. 차분하고 정중한 존댓말(해요체)로, 상황을
. · .
- 1~3( ).
.
- 1~3( ).
. ("만루 무득 실화냐", "느낌 안 좋음")
1~2 .
- . , · .
· ("건전한 응원 문화" ) .
- "그럴 만한 장면이었죠" .
- ·· ( ).
- · ,
· · .
.
- · ( ) . ··
1~2 (: "
. , "). ·
. "품격 있는 응원"·"건전한 응원 문화" , "
"·" "·" " ·
( · ).
- · . "짹" ( ).
- "OO 님" , .
- ·KBO· . ·

View File

@ -131,6 +131,9 @@ async function loadHistory(
getRecentMessages(uid, threadId, fetchLimit),
]);
const minCreatedAt = Date.now() - config.historyMaxAgeHours * 3600 * 1000;
// TODO(historyCutAt): 이 값을 set하는 코드가 아직 없어 항상 no-op(?? 0). stylePack/페르소나
// 교체 시점에 스레드 문서에 historyCutAt를 찍어야 활성화된다(§5.5 표시/입력 분리). 그전까지
// 실제 문맥 분리는 스레드(팀)·턴수·나이 3가지로만 이뤄짐.
const cutAt = thread?.historyCutAt?.toMillis() ?? 0;
const asc = [...recentDesc].reverse();

View File

@ -27,6 +27,7 @@ export interface ChatThreadDoc {
lastMessageAt: Timestamp;
expireAt: Timestamp;
/** 페르소나 교체 시 모델 입력 절단 지점(표시 이력은 유지). §5.5 표시/입력 분리. */
// TODO(historyCutAt): 미배선 — 읽기만 있고 이 필드를 set하는 코드가 없음. 페르소나/stylePack 교체 시 set 필요.
historyCutAt?: Timestamp;
}

View File

@ -193,5 +193,39 @@ describe("chatContextService", () => {
// 팀색(잠실)은 스타일과 무관하게 그대로 유지
expect(co).toContain("잠실");
});
it("commentator는 훈계·품격응원 설교 대신 점수판 농담, 길이 매칭을 지킨다", () => {
const co = assembleSystemPrompt(
{ ...DEFAULT_CHAT_CONFIG, stylePack: "commentator" },
ctx({ teamCode: TeamCode.OB, teamName: "두산 베어스" }),
);
expect(co).toContain("훈계하지 않는다"); // 감정 교정·도발 훈계 금지
expect(co).toContain("점수판"); // 라이벌 도발은 점수판 농담으로 낮춤
expect(co).toContain("건전한 응원 문화"); // "품격/건전한 응원" 설교 멘트 금지 규칙 존재
expect(co).toContain("답 길이를 입력 길이에 맞춘다"); // 짧은 입력엔 짧게
});
it("라이브 시청 암시 금지·일정탭 라이브 안내 금지·홈구장 장소 단정 금지는 전 스타일 공통(COMMON)", () => {
// 스타일과 무관하게 COMMON에 들어가므로 두 스타일 모두에서 보장된다.
for (const stylePack of ["bestfriend", "commentator"]) {
const sys = assembleSystemPrompt(
{ ...DEFAULT_CHAT_CONFIG, stylePack },
ctx({ teamCode: TeamCode.OB, teamName: "두산 베어스" }),
);
expect(sys).toContain("실시간 시청 암시"); // "방금 봤다/같이 보고 있다/지켜보고 있다" 금지
expect(sys).toContain("중계나 공식 기록"); // 라이브 스코어는 일정 탭이 아니라 외부로 유도
expect(sys).toContain("팀 홈구장명"); // 홈구장(잠실)을 오늘 경기 장소로 끌어쓰지 말 것
}
});
it("bestfriend는 결과 전제를 의심·캐묻지 않고 그 안에서 받아치되 구체 수치는 날조하지 않는다", () => {
const bf = assembleSystemPrompt(
DEFAULT_CHAT_CONFIG,
ctx({ teamCode: TeamCode.OB, teamName: "두산 베어스" }),
);
expect(bf).toContain("초를 치지 않는다"); // 승리 전제를 의심하며 초 치지 않음
expect(bf).toContain("지어내거나 각색하지 않는다"); // 구체 수치 날조 금지(#15 유지)
expect(bf).not.toContain("일단 앉아라, 같이 보자"); // 라이브 co-watching 예시 제거됨
});
});
});