feat: v2.2.74 → v2.2.82 — chunked writer + 코드 리뷰 패치 + /youtube 확장
주요 변경: [chunked writer 아키텍처 (v2.2.74~v2.2.75)] - 5-stage 다중 에이전트(planner/researcher/reflector/writer/synthesizer) 파이프라인 제거 → 단일 ChunkedWriter 의 outline → section[N] → polish 3-step 으로 교체. 본문 분석에서 추상화 손실 / 토큰 폭증 문제 해소 - 답변 길이 자동 분기: 짧은 prompt 는 fast-path direct 1회 호출, 본문 분석은 chunked. outline 빈 배열도 direct 폴백 [코드 리뷰 9개 항목 일괄 패치 (v2.2.76)] - /research polling hang 방어 (heartbeat + status 정규화 + 연속 실패 abort) - 회사 모드 dispatcher abort 신호를 AIService.chat 까지 전달 - bridgeFetch 에 onHeartbeat 콜백 도입 (slow endpoint 사용자 친화적) - dead code 정리: reflectionPersister.ts 제거 + enableReflection 등 좀비 config 키 - parseOutline 의 empty vs fallback reason 명시적 분리 - chatHandlers 의 회사 모드 케이스 ~325줄을 src/sidebar/companyHandlers.ts 로 분리 - Intent Alignment 라운드 한도 도달 시 smart 모드 자동 진행 - LM Studio doSwitch unload 실패 시 currentModel 정리 + load 강행 - retrieval informationDensity → queryCoverage 정합화 [/youtube 채널 지원 (v2.2.77~v2.2.82)] - 채널/플레이리스트 URL 자동 감지 + n:N 으로 영상 개수 지정 (최대 50) - 채널 루트 URL 에 /videos 탭 자동 append (yt-dlp enumeration 정상화) - 영상별 순차 처리 (queue 패턴) + i/N 진행 표시 + 마지막 통계 요약 - mode:info / mode:benchmark / mode:both 분석 모드 분기 - info: 영상 내용을 지식 카드로 추출 (튜토리얼·강의·뉴스용) - benchmark: 4-렌즈 대본 역기획서 (콘텐츠 제작 벤치마크용) - both: 둘 다 (기본) - bare keyword 도 허용: /youtube <url> n:1 info - bridge 에러 메시지 [object Object] 깨짐 수정 (구조화 에러 추출) - "패키지 없음" 등 환경 의존성 에러에 자동 가이드 첨부 [Astra: Setup Datacollect Dependencies 명령 추가 (v2.2.80)] - Python 자동 감지 + yt-dlp / youtube-transcript-api 자동 설치 - macOS PEP 668 환경 자동 폴백 (--user --break-system-packages) - /youtube 등에서 패키지 미설치 감지 시 "Install Now" 버튼 notification [테스트] - tests/agentEngine.test.ts 를 chunked flow 에 맞춰 전체 재작성 - tests/resilience_stress.test.ts Scenario B/D 를 role-aware mock 으로 갱신 - 399/399 통과 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+136
-162
@@ -63,7 +63,7 @@ export abstract class BaseAgent {
|
||||
}
|
||||
|
||||
const data = await response.json() as any;
|
||||
|
||||
|
||||
// 강력한 응답 추출 (Multi-path parsing)
|
||||
let content = '';
|
||||
if (data.message?.content) content = data.message.content;
|
||||
@@ -71,7 +71,7 @@ export abstract class BaseAgent {
|
||||
else if (data.choices?.[0]?.text) content = data.choices[0].text;
|
||||
else if (data.response) content = data.response;
|
||||
else if (typeof data === 'string') content = data;
|
||||
|
||||
|
||||
return content || '';
|
||||
} catch (error: any) {
|
||||
clearTimeout(timeoutId);
|
||||
@@ -99,175 +99,149 @@ function anySignal(signals: AbortSignal[]): AbortSignal {
|
||||
return controller.signal;
|
||||
}
|
||||
|
||||
export class PlannerAgent extends BaseAgent {
|
||||
private readonly persona = `You are the [Master Strategist & Planner].
|
||||
Your sole purpose is to transform vague requests into flawless, high-resolution execution blueprints.
|
||||
- THINKING PROCESS: You must analyze the request from multiple angles (technical, logical, structural).
|
||||
- OUTPUT RULE: You MUST output a structured <blueprint> using Markdown.
|
||||
- COMPONENTS: Each blueprint must have [Objective], [Core Challenges], [Data Requirements], and [Step-by-Step Research Tasks].
|
||||
- CONSTRAINT: Do not be vague. Use professional terminology. If the request is too simple, expand it with relevant technical considerations.`;
|
||||
|
||||
async execute(input: string, brainContext?: string, signal?: AbortSignal, options?: AgentExecuteOptions): Promise<string> {
|
||||
const wrappedInput = `### SYSTEM INSTRUCTION: GENERATE EXECUTION BLUEPRINT
|
||||
1. Target Goal: ${input}
|
||||
2. Available Knowledge Base & Policy: ${brainContext}
|
||||
3. Mission: Create a comprehensive research roadmap.`;
|
||||
return this.callLLM(this.persona, wrappedInput, signal);
|
||||
}
|
||||
}
|
||||
|
||||
export class ResearcherAgent extends BaseAgent {
|
||||
private readonly persona = `You are the [Senior Technical Researcher].
|
||||
Your mission is to extract, filter, and synthesize critical data based on a strategic blueprint.
|
||||
- DATA INTEGRITY: Only provide high-quality, verified-style information.
|
||||
- FORMAT: Use [Key Facts], [Technical Deep-Dive], and [Summary of Knowledge] sections.
|
||||
- CRITICAL THINKING: Identify gaps in the plan and provide extra insights to fill those gaps.
|
||||
- NO FLUFF: Be concise but extremely dense with information.`;
|
||||
|
||||
async execute(input: string, brainContext?: string, signal?: AbortSignal, options?: AgentExecuteOptions): Promise<string> {
|
||||
const wrappedInput = `### SYSTEM INSTRUCTION: DATA HARVESTING
|
||||
1. Blueprint to Follow: ${input}
|
||||
2. Contextual Constraints & Policy: ${brainContext}
|
||||
3. Mission: Provide a dense summary of facts and technical insights.`;
|
||||
return this.callLLM(this.persona, wrappedInput, signal);
|
||||
}
|
||||
}
|
||||
|
||||
export class WriterAgent extends BaseAgent {
|
||||
// [5-stage pipeline] Writer는 이제 "Drafter" 역할: 빠르게 1차 초안만 생성한다.
|
||||
// 최종 다듬기/요약/critique 반영은 후속 SynthesizerAgent가 담당하므로,
|
||||
// 작은 모델이 한 번에 모든 것을 끝내려 컨텍스트를 폭주시키는 일이 없도록 한다.
|
||||
private readonly persona = `You are the [Section Drafter].
|
||||
Your goal is to produce a STRUCTURED FIRST DRAFT that the downstream Synthesizer will polish.
|
||||
- SCOPE: Cover each major topic from the research as its own section. Each section starts with a short plain-text label on its own line (e.g. "잘된 점", "부족한 점") — NO "#", "##", "**", "__", ">" markers. Use "- " for bullets, never "* ".
|
||||
- DENSITY: Pack facts; skip flowery prose, executive summaries, and closing remarks (the Synthesizer adds those).
|
||||
- TONE: Plain, factual, developer-readable Korean.
|
||||
- BREVITY: Keep each section tight — better to leave the Synthesizer something to merge than to run out of tokens mid-section.
|
||||
- SELF-CORRECTION: When a [REFLECTION CRITIQUE] block is provided, address each listed gap inline in the relevant section. Do not silently ignore the critique.
|
||||
- LANGUAGE: KOREAN.`;
|
||||
|
||||
async execute(input: string, originalRequest?: string, signal?: AbortSignal, options?: AgentExecuteOptions): Promise<string> {
|
||||
// [Astra v4.0] Advisor 모드 처리
|
||||
if (options?.config?.role === 'advisor') {
|
||||
const advisorPersona = `You are the [Strategic Proactive Advisor].
|
||||
Analyze the provided report and suggest 3 high-impact next actions for the user.
|
||||
- Focus on decision forks, risk mitigation, or immediate implementation steps.
|
||||
- Be extremely concrete and actionable.
|
||||
- Respond in KOREAN.`;
|
||||
return this.callLLM(advisorPersona, input, signal);
|
||||
}
|
||||
|
||||
// Fix 3: Trim input if it's too long (Basic Context Diet)
|
||||
const trimmedData = input.length > 8000 ? input.substring(0, 8000) + '... [Data Trimmed for Performance]' : input;
|
||||
|
||||
const policy = options?.context || '';
|
||||
const reflection = options?.priorResults?.reflection;
|
||||
// Reflector 결과가 있으면 별도 블록으로 주입. 길이 4000자 cap (Writer 입력 비대화 방지).
|
||||
const reflectionBlock = reflection && reflection.trim().length > 0
|
||||
? `\n5. [REFLECTION CRITIQUE — must be addressed]:\n${reflection.length > 4000 ? reflection.substring(0, 4000) + '... [Critique Trimmed]' : reflection}`
|
||||
: '';
|
||||
|
||||
const wrappedInput = `### SYSTEM INSTRUCTION: SECTIONED DRAFT
|
||||
1. Gathered Research Data: ${trimmedData}
|
||||
2. User's Original Objective: ${originalRequest}
|
||||
3. Applied Knowledge & Filtering Policy: ${policy}
|
||||
4. Mission: Produce a STRUCTURED FIRST DRAFT in KOREAN — section per topic, factual bullets allowed.
|
||||
Do NOT add a final executive summary or closing remarks; the Synthesizer will handle those.${reflectionBlock}`;
|
||||
return this.callLLM(this.persona, wrappedInput, signal);
|
||||
}
|
||||
/**
|
||||
* Section outline shape produced by ChunkedWriter in the 'outline' role.
|
||||
* Tokens are kept minimal — heading is what the section is about, scope tells
|
||||
* the next call what facts to keep inside that section so adjacent sections
|
||||
* don't duplicate content.
|
||||
*/
|
||||
export interface SectionOutline {
|
||||
heading: string;
|
||||
scope: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* [5-stage pipeline] SynthesizerAgent
|
||||
* Drafter가 작성한 1차 초안을 받아 최종 사용자 답변으로 다듬는다.
|
||||
* - 입력이 "이미 정리된 draft" 라서 컨텍스트가 작다 → 작은 로컬 모델도 한 번에 처리 가능.
|
||||
* - 역할은 (a) 도입 한 줄 (b) 섹션 흐름 정리 (c) 결론/제안 한 단락. 새로운 사실을 만들지 않는다.
|
||||
* - Reflector critique이 함께 전달되면, 그 항목들이 답변에 정말 반영되었는지 한 번 더 점검한다.
|
||||
* ChunkedWriter — single-agent replacement for the old 5-stage pipeline.
|
||||
*
|
||||
* Why this exists: the old pipeline (planner → researcher → reflector → writer
|
||||
* → synthesizer) was different *personas* in series, which (a) burned tokens
|
||||
* by repeating context at every hop and (b) drifted away from the user's
|
||||
* actual request because intermediate agents only saw earlier agents'
|
||||
* abstractions — never the original message. The user's intent was simpler:
|
||||
* **split the *answer* into chunks so each LLM call stays under the token
|
||||
* cap, then join.** That's what this class does.
|
||||
*
|
||||
* Flow inside `AgentEngine.runMission`:
|
||||
* 1. role='outline' → 1 LLM call returns a JSON list of section outlines
|
||||
* (N = 1..MAX, the model decides based on expected
|
||||
* output length).
|
||||
* 2. role='section' → N LLM calls, one per outline entry, each given the
|
||||
* original prompt + this section's scope + already-
|
||||
* written sections (truncated) so it can avoid
|
||||
* repeating earlier content.
|
||||
* 3. role='polish' → 1 LLM call takes the joined draft and produces a
|
||||
* final clean copy (fixes typos, removes
|
||||
* hallucinations / unsupported claims, smooths flow).
|
||||
*
|
||||
* Every role uses the *same* model — no persona mismatch, no agent-to-agent
|
||||
* abstraction loss. The only thing that changes is the per-call system
|
||||
* prompt picked here based on `options.config.role`.
|
||||
*/
|
||||
export class SynthesizerAgent extends BaseAgent {
|
||||
private readonly persona = `You are the [Final Editor & Synthesizer].
|
||||
You receive a structured FIRST DRAFT (already broken into sections) plus the user's original request and (optionally) a reflection critique.
|
||||
Your only job is to produce the FINAL user-facing answer.
|
||||
export class ChunkedWriter extends BaseAgent {
|
||||
/** Hard cap on section count regardless of what the outline model returns. */
|
||||
static readonly MAX_SECTIONS = 5;
|
||||
|
||||
[OUTPUT FORMAT — 7 hard rules — these override every other formatting habit]
|
||||
R1. CONCLUSION FIRST. The very first sentence is the conclusion / verdict / recommendation. No greeting, no "분석해보겠습니다", no scene-setting paragraph, no "핵심 요약" label line on top. Just the conclusion as sentence 1. A reader who stops after sentence 1 must know what you decided.
|
||||
R2. AT MOST 3 SECTIONS. Total. A section = a label line + body, or a clearly separated numbered group. If the answer fits without sections, use none. Three is the ceiling, not a target.
|
||||
R3. NO REPETITION. Each sentence carries new information. If you said it in the conclusion, do NOT restate it in a later section.
|
||||
R4. BOLD ≤ 3 INSTANCES. Across the entire answer, use bold at most 3 times — reserve it for truly load-bearing words (file name, verdict word, hard number). Most answers should have zero.
|
||||
R5. JUDGE WITHOUT ASKING. If a defensible decision is reachable from the draft + original request, deliver it and act. Do NOT ask permission, do NOT bounce the question back.
|
||||
R6. ASK ONE QUESTION ONLY WHEN: (a) the path forks into two materially different directions and user intent is unknown, OR (b) the next step is irreversible (delete, force-push, drop table, overwrite uncommitted work, send external message). One plain sentence on its own line at the end. No "핵심 확인 질문" label, no "질문 의도", no follow-ups.
|
||||
R7. GUESS-AND-ACT WITH STATED ASSUMPTION. If a detail is missing but a reasonable guess exists, guess and act, declaring the assumption in one line prefixed "가정:".
|
||||
private readonly outlinePersona = `You are a concise editor planning the structure of a Korean answer.
|
||||
Decide how many sections the answer needs (0..${ChunkedWriter.MAX_SECTIONS}). Pick the *smallest* number that still covers the user's request well — a short factual question should be 0-1 section, a meaty analysis 3-5.
|
||||
|
||||
[PLAIN TEXT]
|
||||
- NEVER emit "#", "##", "###", "__", "> " markers. Section labels are plain text on their own line.
|
||||
- Bullets: "- " only. No "* " / "• ".
|
||||
- No tables. No HTML.
|
||||
- Inline code with backticks is OK (e.g. \`src/agent.ts\`). Triple-backtick code blocks only for actual code.
|
||||
Output STRICTLY a JSON array of objects: \`[{"heading": "...", "scope": "..."}]\`. No prose, no fences, no leading text.
|
||||
- 🟢 **빈 배열 \`[]\`** = "쪼갤 필요 없음". 사용자 질문이 간단해서 단일 LLM 호출로 즉답이 더 빠르고 자연스러울 때 (예: 단순 사실 질문, 짧은 코드 한 줄, 정의 묻기). 시스템이 이걸 받으면 outline·section 단계 건너뛰고 1회 직답으로 처리한다.
|
||||
- heading: a short Korean section label (≤ 24 chars). For 1-section answers, set heading to "본문".
|
||||
- scope: one Korean sentence describing exactly what facts/points belong inside this section so adjacent sections don't overlap.
|
||||
|
||||
[CONTENT]
|
||||
- Preserve every factual claim from the draft. Do NOT invent new facts, do NOT add hidden reasoning, do NOT write meta-commentary.
|
||||
- DO NOT EMIT: <think>, <analysis>, <|channel|> markers, "Thinking Process:", planning notes, or any hidden reasoning.
|
||||
- If a [REFLECTION CRITIQUE] is provided, verify each item is addressed. If something is missing, say so explicitly rather than fabricating coverage.
|
||||
- LANGUAGE: KOREAN. Tone: direct, technical, developer-friendly.`;
|
||||
판단 기준:
|
||||
- 답변이 한 단락 (대략 3~5문장) 이내로 완결 가능 → \`[]\`
|
||||
- 본문 분석·여러 측면 비교·구조화된 보고서가 필요 → N개 섹션
|
||||
|
||||
async execute(input: string, originalRequest?: string, signal?: AbortSignal, options?: AgentExecuteOptions): Promise<string> {
|
||||
const draft = input.length > 12000 ? input.substring(0, 12000) + '... [Draft Trimmed]' : input;
|
||||
const reflection = options?.priorResults?.reflection;
|
||||
const reflectionBlock = reflection && reflection.trim().length > 0
|
||||
? `\n4. [REFLECTION CRITIQUE — verify the draft addresses each item]:\n${reflection.length > 3000 ? reflection.substring(0, 3000) + '... [Critique Trimmed]' : reflection}`
|
||||
If the user attached source content (article/code/log) the sections must cover *that content*, not analysis methodology.`;
|
||||
|
||||
private readonly sectionPersona = `You are writing ONE section of a longer Korean answer. You will be given:
|
||||
- the user's original request (possibly with attached content),
|
||||
- this section's heading + scope,
|
||||
- the full outline (for context only — DO NOT write other sections),
|
||||
- already-written previous sections (so you can avoid repeating them).
|
||||
|
||||
Rules:
|
||||
- Stay strictly inside this section's scope. Do NOT cover other outline entries.
|
||||
- Korean, plain markdown (no top-level "#" — the heading will be added by the joiner).
|
||||
- Pack facts. Avoid filler / executive summaries / closing remarks (the polish pass adds those).
|
||||
- If the user attached source content, cite from it; do not invent facts.
|
||||
- Do NOT output the heading itself — only the body of this section.`;
|
||||
|
||||
private readonly polishPersona = `You are the final editor producing the user-facing Korean answer from a sectioned draft.
|
||||
|
||||
Job:
|
||||
1. Fix typos, broken markdown, inconsistent terminology.
|
||||
2. Remove unsupported claims / hallucinations: if a sentence asserts a fact that isn't grounded in the user's request (or the earlier sections themselves), delete it. Better to be short than wrong.
|
||||
3. Smooth section transitions and remove duplicated information across sections.
|
||||
4. Open with the conclusion / key takeaway in the first sentence (no "분석해보겠습니다", no preamble).
|
||||
5. Preserve every factually grounded claim from the draft. Don't invent new facts.
|
||||
|
||||
Output rules:
|
||||
- Korean. Plain markdown. Section labels as plain text on their own line — no "#", "##".
|
||||
- Bullets with "- " only. No tables, no HTML, no triple-bar separators.
|
||||
- If the draft already has a sensible structure, keep it; only refactor when sections clearly overlap or contradict.
|
||||
- DO NOT emit hidden reasoning (<think>, "Thinking:", etc.).`;
|
||||
|
||||
/**
|
||||
* Single-pass 직답 persona. 짧은 질문·정의 묻기·간단한 사실 확인처럼
|
||||
* 쪼갤 필요 없는 입력을 1회 호출로 끝낸다. outline → section → polish 의
|
||||
* 3회 LLM 호출을 통째로 우회 → 작은 모델로 즉답 가능.
|
||||
*/
|
||||
private readonly directPersona = `You are answering a Korean user request in one shot. No outline, no drafting — just the final answer.
|
||||
|
||||
Rules:
|
||||
- 첫 문장이 결론 / 직답이다. "분석해보겠습니다" "좋은 질문입니다" 같은 서문 금지.
|
||||
- Korean. Plain markdown — "#", "##" 같은 헤더 금지, "- " bullet 만 허용. No tables, no HTML.
|
||||
- 짧은 질문엔 짧은 답. 한 문장으로 충분하면 한 문장.
|
||||
- 사용자가 본문(코드·기사·로그)을 첨부했으면 그 본문에서 인용. 본문에 없는 사실 지어내지 말 것.
|
||||
- 추론 과정·"Thinking:"·<think> 노출 금지.`;
|
||||
|
||||
async execute(input: string, context?: string, signal?: AbortSignal, options?: AgentExecuteOptions): Promise<string> {
|
||||
const role = (options?.config?.role as string | undefined) || 'section';
|
||||
switch (role) {
|
||||
case 'outline':
|
||||
return this.callLLM(this.outlinePersona, this.buildOutlinePrompt(input, context), signal);
|
||||
case 'polish':
|
||||
return this.callLLM(this.polishPersona, this.buildPolishPrompt(input, options), signal);
|
||||
case 'direct':
|
||||
return this.callLLM(this.directPersona, this.buildDirectPrompt(input, context), signal);
|
||||
case 'section':
|
||||
default:
|
||||
return this.callLLM(this.sectionPersona, this.buildSectionPrompt(input, context, options), signal);
|
||||
}
|
||||
}
|
||||
|
||||
private buildOutlinePrompt(userRequest: string, brainContext?: string): string {
|
||||
const ctx = brainContext && brainContext.trim().length > 0
|
||||
? `\n\n[보조 지식 컨텍스트 — 답변에 직접 인용하기보단 분할 결정에만 참고]\n${brainContext.substring(0, 1200)}`
|
||||
: '';
|
||||
return `[사용자 요청 — 본문이 포함돼 있다면 그게 1차 자료입니다]\n${userRequest}${ctx}\n\n위 요청에 대한 답변을 ${ChunkedWriter.MAX_SECTIONS}개 이내의 섹션으로 어떻게 나눌지 JSON 배열로만 출력하세요.`;
|
||||
}
|
||||
|
||||
const wrappedInput = `### SYSTEM INSTRUCTION: FINAL SYNTHESIS
|
||||
1. User's Original Request: ${originalRequest || '(unavailable)'}
|
||||
2. Structured Draft (from Drafter — your input to polish):
|
||||
${draft}
|
||||
3. Mission: Produce the FINAL user-facing answer in KOREAN. Do not restart from scratch — polish, smooth, and conclude.${reflectionBlock}`;
|
||||
return this.callLLM(this.persona, wrappedInput, signal);
|
||||
}
|
||||
}
|
||||
|
||||
export class ReflectorAgent extends BaseAgent {
|
||||
private readonly persona = `You are the [Internal Critic & Self-Reflection Officer].
|
||||
Your sole role is META-COGNITION: stress-test the plan and the research output BEFORE the Writer commits to a final report.
|
||||
- POSTURE: Skeptical, rigorous, blunt. You are looking for what is WRONG, not what is right.
|
||||
- DO NOT: rewrite the report, add new content, or speculate beyond the evidence provided.
|
||||
- DO: surface gaps, unsupported claims, contradictions, drift from the original objective, and missing perspectives.
|
||||
- OUTPUT STRICTLY in this Markdown shape (Korean):
|
||||
## 🧭 Alignment with Objective
|
||||
- <원래 요청 대비 일치/이탈 평가>
|
||||
## 🕳 Gaps & Missing Evidence
|
||||
- <plan에는 있지만 research가 다루지 않은 항목>
|
||||
## ⚖️ Contradictions / Conflicts
|
||||
- <research 내부 또는 brain context와의 모순; 없으면 "발견되지 않음">
|
||||
## 🚨 Unsupported / Weak Claims
|
||||
- <근거가 빈약하거나 일반화된 진술>
|
||||
## ✅ Guidance for Writer
|
||||
- <Writer가 최종 리포트에서 반드시 보정해야 할 3~5개 구체 지시>
|
||||
- CONSTRAINT: 최대 500단어. 새 지식을 만들지 말고, 제공된 자료에서만 판단할 것.`;
|
||||
|
||||
async execute(input: string, _context?: string, signal?: AbortSignal, options?: AgentExecuteOptions): Promise<string> {
|
||||
const plan = options?.priorResults?.plan || '(plan unavailable)';
|
||||
const research = input;
|
||||
const originalPrompt = options?.priorResults?.originalPrompt || '(original prompt unavailable)';
|
||||
const brainContext = options?.context || '';
|
||||
|
||||
// Reflector 는 중간 단계이므로 비대한 입력을 방지하기 위해 각 섹션을 cap.
|
||||
const cap = (s: string, n: number) => s.length > n ? s.substring(0, n) + '... [trimmed]' : s;
|
||||
|
||||
const wrappedInput = `### SYSTEM INSTRUCTION: SELF-REFLECTION PASS
|
||||
1. Original User Objective:
|
||||
${cap(originalPrompt, 1500)}
|
||||
|
||||
2. Planner Blueprint:
|
||||
${cap(plan, 3000)}
|
||||
|
||||
3. Researcher Output (to be critiqued):
|
||||
${cap(research, 5000)}
|
||||
|
||||
4. Knowledge / Brain Context (for cross-check only — do not invent beyond this):
|
||||
${cap(brainContext, 2000)}
|
||||
|
||||
5. Mission: Run a single rigorous reflection pass and output the structured critique exactly as specified by your persona.`;
|
||||
return this.callLLM(this.persona, wrappedInput, signal);
|
||||
private buildSectionPrompt(input: string, brainContext?: string, options?: AgentExecuteOptions): string {
|
||||
const prior = options?.priorResults ?? {};
|
||||
const heading = prior.sectionHeading ?? '본문';
|
||||
const scope = prior.sectionScope ?? '사용자 요청 전체';
|
||||
const outlineJoined = prior.outlineSummary ?? '';
|
||||
const prev = prior.prevSectionsTrimmed ?? '';
|
||||
const originalPrompt = prior.originalPrompt ?? input;
|
||||
const ctx = brainContext && brainContext.trim().length > 0
|
||||
? `\n\n[보조 지식 컨텍스트]\n${brainContext.substring(0, 2000)}`
|
||||
: '';
|
||||
return `[사용자 원본 요청]\n${originalPrompt}\n\n[이 섹션 정보]\nheading: ${heading}\nscope: ${scope}\n\n[전체 outline — 다른 섹션은 다루지 마세요]\n${outlineJoined}\n\n[이미 작성된 섹션들 — 중복 금지]\n${prev || '(없음 — 첫 섹션)'}${ctx}\n\n위 scope만 다루는 섹션 본문을 작성하세요. heading 줄은 출력하지 말고 본문만.`;
|
||||
}
|
||||
|
||||
private buildPolishPrompt(draft: string, options?: AgentExecuteOptions): string {
|
||||
const prior = options?.priorResults ?? {};
|
||||
const originalPrompt = prior.originalPrompt ?? '(원본 요청 없음)';
|
||||
return `[사용자 원본 요청]\n${originalPrompt}\n\n[섹션별 초안 — 이것을 다듬어 최종 답변으로]\n${draft}\n\n위 초안을 사용자에게 보낼 최종본으로 다듬으세요. 새 사실 추가 금지, 근거 없는 주장은 제거.`;
|
||||
}
|
||||
|
||||
private buildDirectPrompt(userRequest: string, brainContext?: string): string {
|
||||
const ctx = brainContext && brainContext.trim().length > 0
|
||||
? `\n\n[보조 지식 컨텍스트 — 필요할 때만 인용]\n${brainContext.substring(0, 2000)}`
|
||||
: '';
|
||||
return `[사용자 요청]\n${userRequest}${ctx}\n\n위 요청에 대한 최종 답변을 1회로 끝내세요.`;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user