Tune the Gemini chat provider and cache-optimize the system prompt.
기본 모델을 gemini-3.1-flash-lite로, 전 모델에 공평한 24초 호출 예산과 2048 토큰 출력 한도로 맞추고, Gemini 3 모델은 thinkingLevel을 쓴다. 개인화 값(닉네임·지식수준)을 공통 프롬프트에서 빼서 모든 유저 공통의 정적 prefix로 만들어 Vertex 교차-유저 implicit caching 히트율을 높인다.
This commit is contained in:
parent
1ab744e660
commit
d3c6fddb58
@ -17,10 +17,14 @@ export const CRISIS_MARKER = "[[CRISIS]]";
|
||||
|
||||
/**
|
||||
* [블록 1] 공통 시스템 프롬프트 (페르소나 문서 2.3 전문).
|
||||
* `{{displayName}}`, `{{knowledgeLevel}}`은 주입 변수(2.2).
|
||||
*
|
||||
* 캐시 친화성: 개인화 값(닉네임·지식수준)은 이 블록에 넣지 않고 [사용자 컨텍스트]
|
||||
* 블록(USER_CONTEXT_TEMPLATE)에만 둔다. 이 블록을 모든 유저 공통의 정적 prefix로
|
||||
* 만들어 Vertex implicit caching의 교차-유저 히트율을 높이기 위함이다(값을 끼워넣으면
|
||||
* 유저마다 prefix가 달라져 공유 캐시가 깨진다).
|
||||
*/
|
||||
export const COMMON_SYSTEM_PROMPT = `너는 "짹"이다. KBO 승부예측 앱 "팬잇(Panit)"에 사는 참새 캐릭터이자
|
||||
사용자 {{displayName}}의 야구 친구다. 어느 팀의 짹인지는 뒤에 붙는
|
||||
사용자의 야구 친구다(사용자의 닉네임은 [사용자 컨텍스트]에 있다). 어느 팀의 짹인지는 뒤에 붙는
|
||||
[팀 페르소나]가 정한다. [팀 페르소나]와 [사용자 컨텍스트]를 함께
|
||||
사용해 대화한다.
|
||||
|
||||
@ -56,8 +60,8 @@ export const COMMON_SYSTEM_PROMPT = `너는 "짹"이다. KBO 승부예측 앱 "
|
||||
무례하게 굴지 않는다. 짹은 자기 팀을 사랑하지만 야구 자체를 더 사랑한다.
|
||||
|
||||
## 2. 지식수준 분기 — 설명 난이도만 바꾼다
|
||||
사용자의 지식수준은 {{knowledgeLevel}}이다. 말투와 캐릭터는 절대 바꾸지
|
||||
말고, "설명하는 방식"만 다음과 같이 조절한다.
|
||||
사용자의 지식수준은 [사용자 컨텍스트]에 명시된다(beginner/casual/expert).
|
||||
말투와 캐릭터는 절대 바꾸지 말고, "설명하는 방식"만 다음과 같이 조절한다.
|
||||
- beginner(야린이): 야구 용어를 쓰면 반드시 한 줄로 풀어서 같이 말한다.
|
||||
(예: "병살, 그러니까 공 하나로 아웃 두 개 잡히는 거") 규칙 설명은 비유와
|
||||
예시 중심으로. 숫자·기록은 최소한만.
|
||||
|
||||
@ -30,9 +30,15 @@ export const DEFAULT_PROVIDER_CONFIG: ChatProviderConfig = {
|
||||
name: "vertex",
|
||||
model: "gemini-3.1-flash-lite",
|
||||
temperature: 0.7,
|
||||
maxOutputTokens: 512,
|
||||
timeoutMs: 12_000,
|
||||
maxRetries: 1,
|
||||
// Gemini 3는 thinking 토큰도 출력 예산에 포함된다 — 512는 thinking이 예산을
|
||||
// 소진해 빈 응답(finishReason=length)을 유발했다. 짧은 캐릭터 응답이라도
|
||||
// thinking + 함수호출 + 답변 여유분으로 넉넉히 잡는다.
|
||||
maxOutputTokens: 2048,
|
||||
// 모든 모델 공평 타임아웃 — 25초 총예산(클라 receiveTimeout 30초 - 여유)에서 쓸 수
|
||||
// 있는 최대 단일 시도. 모델마다 속도가 달라도 동일 예산이라 프로필이 불필요하다
|
||||
// (느린 모델도 24초까지 기다림). 재시도는 예산상 0(24s×1 ≤ 25s).
|
||||
timeoutMs: 24_000,
|
||||
maxRetries: 0,
|
||||
};
|
||||
|
||||
export const DEFAULT_CHAT_CONFIG: ChatConfig = {
|
||||
|
||||
@ -123,13 +123,18 @@ describe("chatContextService", () => {
|
||||
});
|
||||
|
||||
describe("assembleSystemPrompt(§5 조립 순서)", () => {
|
||||
it("공통(변수 치환) + 서버지시 + 팀 페르소나 + 컨텍스트 순으로 조립한다", () => {
|
||||
it("공통(정적) + 서버지시 + 팀 페르소나 + 컨텍스트 순으로 조립한다", () => {
|
||||
const prompt = assembleSystemPrompt(DEFAULT_CHAT_CONFIG, ctx({}));
|
||||
expect(prompt).toContain("사용자 솔방울의 야구 친구다"); // {{displayName}} 치환
|
||||
expect(prompt).toContain("사용자의 지식수준은 casual이다"); // {{knowledgeLevel}} 치환
|
||||
// 캐시 친화: 공통 블록은 비개인화(정적), 개인화 값은 컨텍스트 블록에만
|
||||
expect(prompt).toContain("사용자의 야구 친구다"); // 비개인화 공통(닉네임 미포함)
|
||||
expect(prompt).not.toContain("사용자 솔방울의 야구 친구다"); // 닉네임은 공통에 없음
|
||||
expect(prompt).toContain("- 사용자: 솔방울 / 지식수준: casual"); // 개인화는 컨텍스트 블록
|
||||
expect(prompt).toContain(CHAT_CANARY_TOKEN); // 카나리 포함
|
||||
expect(prompt).toContain("[팀 페르소나 — 한화 이글스 짹 (HH)]"); // HH placeholder
|
||||
expect(prompt.indexOf("[팀 페르소나")).toBeLessThan(prompt.indexOf("[사용자 컨텍스트"));
|
||||
// 실제 블록 헤더("— " 포함)로 순서 검증 — 본문의 참조 문구와 구분
|
||||
expect(prompt.indexOf("[팀 페르소나 — ")).toBeLessThan(prompt.indexOf("[사용자 컨텍스트 — "));
|
||||
// 정적 공통 prefix가 개인화 컨텍스트 블록보다 앞에 와야 캐시 prefix 공유가 성립
|
||||
expect(prompt.indexOf("사용자의 야구 친구다")).toBeLessThan(prompt.indexOf("[사용자 컨텍스트 — "));
|
||||
});
|
||||
|
||||
it("응원팀 미설정이면 기본 짹 블록을 쓴다(2.1)", () => {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user