Update ConnectAI codebase
This commit is contained in:
@@ -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');
|
||||
}
|
||||
@@ -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(근거: 추론)';
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
export {
|
||||
buildDevilSystemPrompt,
|
||||
buildDevilUserPrompt,
|
||||
DevilPromptInput,
|
||||
DEVIL_PERSONA_NAME,
|
||||
} from './devilPrompt';
|
||||
|
||||
export {
|
||||
isDevilAgentEnabled,
|
||||
setDevilAgentEnabled,
|
||||
generateDevilRebuttal,
|
||||
SETTING_KEY,
|
||||
} from './devilService';
|
||||
Reference in New Issue
Block a user