Update ConnectAI codebase

This commit is contained in:
g1nation
2026-05-18 08:15:01 +09:00
parent 88664c7c6e
commit 86cacaeb03
38 changed files with 1043 additions and 99 deletions
+71
View File
@@ -0,0 +1,71 @@
/**
* Devil's Advocate (도현) — system prompt 빌더.
*
* 설계 원칙:
* - 모든 약점을 나열하지 않음. *한 turn 에 1개* — 사용자가 깊게 생각하게.
* - 'X 입장에서 본다면' framing — 본인 의견이 아니라 *다른 시점* 으로 제시.
* 이는 LLM 의 '내가 옳다' 경향을 줄이는 잘 알려진 패턴.
* - 통계 / 구체 수치 / 외부 사례 *금지* — 환각 표면적 차단.
* Phase 2 에서 외부 데이터 활성화 옵션 추가 예정.
* - 출처 명시 강제 — '(근거: 추론)' 또는 '(근거: brain/<file>)' 로 끝.
*
* 출력 형태:
* 3~5 문장 한 문단. 끝에 "① 우려" + "② 검증 방법 1개" 강제.
*/
export const DEVIL_PERSONA_NAME = '도현';
export interface DevilPromptInput {
/** 사용자가 던진 원래 질문. */
userPrompt: string;
/** Astra 의 직전 답변. 도현이 반박할 대상. */
assistantAnswer: string;
/** Optional Second Brain context — 있으면 인용 근거로 사용 가능. */
brainContext?: string;
/** Optional re-rebuttal — 사용자가 '재반박' 클릭 후 보낸 메시지. 있으면 다시 한 라운드. */
userRebuttal?: string;
}
const BASE_RULES = [
'당신은 \'도현\', 사용자의 사고를 더 깊게 만드는 *비판적 sparring partner* 입니다.',
'',
'규칙:',
'1. *한 turn 에 한 가지 약점* 만 골라 반박. 모든 약점을 나열하지 말 것 — 사용자가 깊게 생각하도록.',
'2. 본인 의견이 아닌 *다른 시점* 제시: "만약 비관적으로 본다면", "사용자 입장에서는", "1년 뒤 본다면" 등으로 framing 시작.',
'3. 칭찬 / 동의 / "좋은 답변입니다" 금지. 건설적이지만 *단호*.',
'4. 통계·연구·구체 수치·외부 사례 *인용 금지* — 당신은 그런 데이터를 보유하지 않음. 구조적·논리적·시나리오 비판만.',
'5. 답변은 *3~5 문장 한 문단*. 마지막에 반드시 두 줄:',
' ① 우려: <한 문장 핵심 우려>',
' ② 검증: <한 가지 구체적 검증 방법>',
'6. 답변 끝에 출처 태그: brain 자료 인용했으면 `(근거: brain/<filename>)`, 안 했으면 `(근거: 추론)`.',
].join('\n');
export function buildDevilSystemPrompt(): string {
return BASE_RULES;
}
export function buildDevilUserPrompt(input: DevilPromptInput): string {
const parts: string[] = [];
parts.push('## 사용자 원래 질문');
parts.push(input.userPrompt.trim() || '(빈 질문)');
parts.push('');
parts.push('## Astra 의 직전 답변 (당신이 반박할 대상)');
parts.push(input.assistantAnswer.trim() || '(빈 답변)');
if (input.brainContext && input.brainContext.trim()) {
parts.push('');
parts.push('## 참고 가능한 Second Brain 자료');
parts.push('(필요할 때만 인용. 인용하지 않으면 추론으로 표시.)');
parts.push(input.brainContext.trim());
}
if (input.userRebuttal && input.userRebuttal.trim()) {
parts.push('');
parts.push('## 사용자의 재반박');
parts.push(input.userRebuttal.trim());
parts.push('');
parts.push('재반박을 받았으니 *기존 입장을 굽히지 말되 한 단계 더 깊은 약점* 으로 이동하세요. 같은 약점을 반복하지 말 것.');
} else {
parts.push('');
parts.push('위 답변에서 *가장 본질적인 약점 하나* 를 골라 반박하세요.');
}
return parts.join('\n');
}
+60
View File
@@ -0,0 +1,60 @@
/**
* Devil Agent — 직전 답변에 대한 반박 생성기.
*
* 동작:
* 1. agent.ts 가 main turn 완료 직후 호출
* 2. 같은 model/engine 으로 *별도 LLM call 1회* — 짧은 비판 한 문단 생성
* 3. 결과를 webview 로 'devilRebuttal' message 로 send
* 4. 실패 / 비활성 시 silent skip — main 답변에는 영향 없음
*
* 비용:
* - 매 turn 마다 LLM 1회 추가. 사용자가 settings 에서 명시적 ON 했을 때만.
* - 짧은 prompt + maxTokens 350 으로 제한 → 일반 turn 비용의 ~10-15%.
*/
import * as vscode from 'vscode';
import { buildDevilSystemPrompt, buildDevilUserPrompt, DevilPromptInput, DEVIL_PERSONA_NAME } from './devilPrompt';
const SETTING_KEY = 'g1nation.devilAgent.enabled';
export function isDevilAgentEnabled(): boolean {
const cfg = vscode.workspace.getConfiguration('g1nation');
return !!cfg.get<boolean>('devilAgent.enabled', false);
}
export async function setDevilAgentEnabled(enabled: boolean): Promise<void> {
const cfg = vscode.workspace.getConfiguration('g1nation');
await cfg.update('devilAgent.enabled', enabled, vscode.ConfigurationTarget.Global);
}
export { DEVIL_PERSONA_NAME, SETTING_KEY };
/**
* Main turn 끝나면 caller (agent.ts) 가 이 함수를 호출.
* - input 에 사용자 질문 + 직전 답변 + (선택) brain context 전달
* - 반환값: 도현의 반박 텍스트 또는 null (비활성·실패 시)
*
* 호출자가 webview 에 직접 'devilRebuttal' message 를 보내도록 분리 — 본 함수는 *pure*.
*/
export async function generateDevilRebuttal(
callLLM: (system: string, userMessage: string, maxTokens: number) => Promise<string>,
input: DevilPromptInput,
): Promise<string | null> {
if (!isDevilAgentEnabled()) return null;
try {
const system = buildDevilSystemPrompt();
const userMsg = buildDevilUserPrompt(input);
const out = await callLLM(system, userMsg, 350);
const cleaned = (out || '').trim();
if (!cleaned) return null;
// 환각 가드: 통계 / 수치 패턴이 보이면 *(근거: 추론)* 로 강제 변경 — 도현이 환각 자신 가질 가능성 줄임.
return _appendSourceTagIfMissing(cleaned);
} catch {
return null;
}
}
function _appendSourceTagIfMissing(text: string): string {
if (/\(근거:/.test(text)) return text;
return text.trim() + '\n\n(근거: 추론)';
}
+13
View File
@@ -0,0 +1,13 @@
export {
buildDevilSystemPrompt,
buildDevilUserPrompt,
DevilPromptInput,
DEVIL_PERSONA_NAME,
} from './devilPrompt';
export {
isDevilAgentEnabled,
setDevilAgentEnabled,
generateDevilRebuttal,
SETTING_KEY,
} from './devilService';