초안 에이전트 설계 패턴 - 추론 기술
LangChain, LangGraph, TypeScript를 사용하여 서버리스 환경에서 Chain-of-Thought(CoT), Tree-of-Thought(ToT), Graph-of-Thought(GoT), Self-Reflection 패턴 등의 고급 추론 기술을 구현하는 방법을 배워봅니다.
멘탈 모델: 심포니 오케스트라
추론 기술을 심포니 오케스트라로 생각해보세요. 서로 다른 섹션(추론 패턴)이 협력하여 복잡한 하모니(솔루션)를 만들어냅니다. 지휘자(LangGraph)가 여러 악기(에이전트)를 조정합니다 - 현악기는 선형 추론(CoT)을 처리하고, 목관악기는 변형을 탐색하며(ToT), 금관악기는 피드백을 제공하고(Self-Reflection), 타악기는 리듬(상태 관리)을 유지합니다. 음악가가 독주나 앙상블로 연주할 수 있듯이, 에이전트도 독립적으로 추론하거나 더 풍부한 결과를 위해 협력할 수 있습니다.
기본 예제: Chain-of-Thought 추론
1. 기본 CoT 에이전트 설정
// app/agents/cot-agent/route.ts
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
import { StateGraph, Annotation } from "@langchain/langgraph";
import { HumanMessage, AIMessage, BaseMessage } from "@langchain/core/messages";
import { z } from "zod";
import { map, reduce } from "es-toolkit";
// 추론 상태 정의
const ReasoningState = Annotation.Root({
messages: Annotation<BaseMessage[]>({
reducer: (x, y) => x.concat(y),
default: () => [],
}),
reasoning_steps: Annotation<string[]>({
reducer: (x, y) => x.concat(y),
default: () => [],
}),
final_answer: Annotation<string>({
reducer: (_, y) => y,
default: () => "",
}),
});
// 추론 체인 생성
const model = new ChatGoogleGenerativeAI({
modelName: "gemini-pro",
temperature: 0.7,
});
// CoT 추론 노드
async function reasoningNode(state: typeof ReasoningState.State) {
const cotPrompt = `당신은 추론 에이전트입니다. 문제를 단계별로 분해하세요.
문제: ${state.messages[state.messages.length - 1].content}
이를 단계적으로 생각하세요:
1. 먼저, 우리가 해결하려는 것을 식별
2. 더 작은 부분으로 분해
3. 각 부분 해결
4. 솔루션 결합
응답을 다음 형식으로 작성하세요:
단계 1: [추론]
단계 2: [추론]
...
최종 답변: [답]`;
const response = await model.invoke([
new HumanMessage(cotPrompt)
]);
// 응답 파싱
const content = response.content as string;
const steps = content.match(/단계 \d+: (.+)/g) || [];
const finalAnswer = content.match(/최종 답변: (.+)/)?.[1] || "";
return {
messages: [response],
reasoning_steps: steps,
final_answer: finalAnswer,
};
}
// 그래프 구축
const workflow = new StateGraph(ReasoningState)
.addNode("reason", reasoningNode)
.addEdge("__start__", "reason")
.addEdge("reason", "__end__");
const app = workflow.compile();
// API 라우트 핸들러
export async function POST(request: Request) {
const { question } = await request.json();
const result = await app.invoke({
messages: [new HumanMessage(question)],
});
return Response.json({
reasoning_steps: result.reasoning_steps,
final_answer: result.final_answer,
});
}
기본 CoT 에이전트는 문제를 순차적 추론 단계로 분해합니다. 각 단계는 이전 단계를 기반으로 구축되어 논리적 사고의 연쇄를 생성합니다.
2. React Query를 통한 프론트엔드 통합
// app/components/ReasoningAgent.tsx
'use client';
import { useMutation } from '@tanstack/react-query';
import { useState } from 'react';
import { map } from 'es-toolkit';
interface ReasoningResponse {
reasoning_steps: string[];
final_answer: string;
}
async function queryReasoning(question: string): Promise<ReasoningResponse> {
const response = await fetch('/agents/cot-agent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ question }),
});
return response.json();
}
export function ReasoningAgent() {
const [question, setQuestion] = useState('');
const mutation = useMutation({
mutationFn: queryReasoning,
});
return (
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h2 className="card-title">Chain-of-Thought 추론</h2>
<textarea
className="textarea textarea-bordered"
placeholder="복잡한 질문을 입력하세요..."
value={question}
onChange={(e) => setQuestion(e.target.value)}
/>
<button
className="btn btn-primary"
onClick={() => mutation.mutate(question)}
disabled={mutation.isPending}
>
{mutation.isPending ? '추론 중...' : '추론 시작'}
</button>
{mutation.isSuccess && (
<div className="mt-4 space-y-2">
<div className="text-sm opacity-70">추론 단계:</div>
{map(mutation.data.reasoning_steps, (step, idx) => (
<div key={idx} className="p-2 bg-base-200 rounded">
{step}
</div>
))}
<div className="divider"></div>
<div className="alert alert-success">
<span>{mutation.data.final_answer}</span>
</div>
</div>
)}
</div>
</div>
);
}
React Query는 비동기 상태 관리를 처리하며 UI는 추론 단계를 점진적으로 표시합니다.
고급 예제: ToT와 Self-Reflection을 통한 멀티 에이전트 추론
1. 병렬 탐색을 통한 Tree-of-Thought
// app/agents/advanced-reasoning/tree-of-thought.ts
import { StateGraph, Annotation, Send } from "@langchain/langgraph";
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
import { BaseMessage, HumanMessage, AIMessage } from "@langchain/core/messages";
import { z } from "zod";
import { maxBy, sortBy, filter, map, chunk } from "es-toolkit";
import { RunnableConfig } from "@langchain/core/runnables";
// 사고 노드 구조 정의
const ThoughtSchema = z.object({
id: z.string(),
content: z.string(),
score: z.number(),
parent_id: z.string().optional(),
depth: z.number(),
});
type Thought = z.infer<typeof ThoughtSchema>;
// 사고 트리를 가진 상태 정의
const ToTState = Annotation.Root({
problem: Annotation<string>(),
thoughts: Annotation<Thought[]>({
reducer: (x, y) => [...x, ...y],
default: () => [],
}),
current_depth: Annotation<number>({
reducer: (_, y) => y,
default: () => 0,
}),
best_solution: Annotation<string>({
reducer: (_, y) => y,
default: () => "",
}),
exploration_paths: Annotation<string[][]>({
reducer: (x, y) => [...x, ...y],
default: () => [],
}),
});
const model = new ChatGoogleGenerativeAI({
modelName: "gemini-pro",
temperature: 0.8,
});
// 여러 사고를 병렬로 생성
async function generateThoughts(
state: typeof ToTState.State,
config: RunnableConfig
): Promise<Partial<typeof ToTState.State>> {
const parentThoughts = filter(
state.thoughts,
(t) => t.depth === state.current_depth - 1
);
// 부모 사고가 없으면 원래 문제 사용
const prompts = parentThoughts.length > 0
? map(parentThoughts, (parent) => ({
parent_id: parent.id,
prompt: `이 추론 단계를 고려하여: "${parent.content}"
${state.problem}를 해결하기 위한 3가지 다른 방법을 생성하세요.
창의적이고 다양한 접근법을 탐색하세요.`,
}))
: [{
parent_id: "root",
prompt: `문제: ${state.problem}
이 문제를 해결하기 위한 3가지 다른 초기 접근법을 생성하세요.`,
}];
// 사고를 병렬로 생성
const thoughtPromises = map(prompts, async ({ parent_id, prompt }) => {
const response = await model.invoke([new HumanMessage(prompt)]);
const content = response.content as string;
// 응답에서 여러 사고 파싱
const thoughtTexts = content.split('\n').filter(line => line.trim());
return map(thoughtTexts.slice(0, 3), (text, idx) => ({
id: `${parent_id}-${state.current_depth}-${idx}`,
content: text,
score: 0,
parent_id,
depth: state.current_depth,
}));
});
const allThoughts = (await Promise.all(thoughtPromises)).flat();
return {
thoughts: allThoughts,
};
}
// 품질에 따라 사고 점수 매기기
async function scoreThoughts(
state: typeof ToTState.State
): Promise<Partial<typeof ToTState.State>> {
const currentThoughts = filter(
state.thoughts,
(t) => t.depth === state.current_depth && t.score === 0
);
const scoringPrompt = `문제 "${state.problem}"에 대한 이러한 솔루션 접근법을 평가하세요
다음을 기준으로 0-10점을 매기세요:
- 논리적 일관성
- 해결책으로의 진전
- 창의성
접근법:
${map(currentThoughts, (t, i) => `${i + 1}. ${t.content}`).join('\n')}
점수를 다음과 같이 반환하세요: [score1, score2, ...]`;
const response = await model.invoke([new HumanMessage(scoringPrompt)]);
const scores = JSON.parse((response.content as string).match(/\[[\d,\s]+\]/)?.[0] || "[]");
const scoredThoughts = map(currentThoughts, (thought, idx) => ({
...thought,
score: scores[idx] || 0,
}));
// 점수가 매겨진 사고만 업데이트
const updatedThoughts = map(state.thoughts, (t) => {
const scored = scoredThoughts.find(st => st.id === t.id);
return scored || t;
});
return {
thoughts: updatedThoughts,
};
}
// 낮은 점수 분기 가지치기
async function pruneThoughts(
state: typeof ToTState.State
): Promise<Partial<typeof ToTState.State>> {
const currentThoughts = filter(
state.thoughts,
(t) => t.depth === state.current_depth
);
// 사고의 상위 40% 유지
const sorted = sortBy(currentThoughts, [(t) => -t.score]);
const keepCount = Math.max(2, Math.floor(sorted.length * 0.4));
const keepIds = new Set(map(sorted.slice(0, keepCount), t => t.id));
const prunedThoughts = filter(state.thoughts, (t) =>
t.depth < state.current_depth || keepIds.has(t.id)
);
return {
thoughts: prunedThoughts,
};
}
// 탐색을 계속할지 확인
function shouldContinue(state: typeof ToTState.State): "expand" | "synthesize" {
const MAX_DEPTH = 3;
return state.current_depth < MAX_DEPTH ? "expand" : "synthesize";
}
// 최고의 경로에서 최종 솔루션 합성
async function synthesizeSolution(
state: typeof ToTState.State
): Promise<Partial<typeof ToTState.State>> {
// 최고의 리프 노드 찾기
const leafThoughts = filter(
state.thoughts,
(t) => t.depth === state.current_depth
);
const bestLeaf = maxBy(leafThoughts, (t) => t.score);
if (!bestLeaf) {
return { best_solution: "솔루션을 찾을 수 없습니다" };
}
// 루트까지 추적
const path: Thought[] = [];
let current: Thought | undefined = bestLeaf;
while (current) {
path.unshift(current);
current = state.thoughts.find(t => t.id === current?.parent_id);
}
const synthesisPrompt = `이 추론 경로에서 완전한 솔루션을 합성하세요:
${map(path, (t, i) => `단계 ${i + 1}: ${t.content}`).join('\n')}
${state.problem}에 대한 명확하고 포괄적인 답변을 제공하세요`;
const response = await model.invoke([new HumanMessage(synthesisPrompt)]);
return {
best_solution: response.content as string,
exploration_paths: [map(path, t => t.content)],
};
}
// Tree-of-Thought 워크플로우 구축
export function createToTWorkflow() {
const workflow = new StateGraph(ToTState)
.addNode("generate", generateThoughts)
.addNode("score", scoreThoughts)
.addNode("prune", pruneThoughts)
.addNode("synthesize", synthesizeSolution)
.addEdge("__start__", "generate")
.addEdge("generate", "score")
.addEdge("score", "prune")
.addConditionalEdges(
"prune",
shouldContinue,
{
expand: "generate",
synthesize: "synthesize",
}
)
.addEdge("synthesize", "__end__");
return workflow.compile();
}
Tree-of-Thought는 여러 추론 경로를 병렬로 탐색하고, 최적의 솔루션을 찾기 위해 점수를 매기고 가지치기를 합니다.
2. 솔루션 개선을 위한 Self-Reflection 에이전트
// app/agents/advanced-reasoning/self-reflection.ts
import { StateGraph, Annotation } from "@langchain/langgraph";
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
import { BaseMessage, HumanMessage } from "@langchain/core/messages";
import { z } from "zod";
import { reduce, filter, last } from "es-toolkit";
// 비판 히스토리를 가진 리플렉션 상태
const ReflectionState = Annotation.Root({
problem: Annotation<string>(),
current_solution: Annotation<string>({
reducer: (_, y) => y,
}),
critiques: Annotation<Array<{
critique: string;
suggestions: string[];
score: number;
}>>({
reducer: (x, y) => [...x, ...y],
default: () => [],
}),
iteration: Annotation<number>({
reducer: (_, y) => y,
default: () => 0,
}),
final_solution: Annotation<string>({
reducer: (_, y) => y,
default: () => "",
}),
});
const model = new ChatGoogleGenerativeAI({
modelName: "gemini-pro",
temperature: 0.7,
});
// 액터: 초기 솔루션 생성
async function generateSolution(
state: typeof ReflectionState.State
): Promise<Partial<typeof ReflectionState.State>> {
const lastCritique = last(state.critiques);
const prompt = lastCritique
? `문제: ${state.problem}
이전 솔루션: ${state.current_solution}
비판: ${lastCritique.critique}
제안: ${lastCritique.suggestions.join(', ')}
이러한 포인트를 해결하는 개선된 솔루션을 생성하세요.`
: `이 문제를 해결하세요: ${state.problem}
명확한 추론과 함께 자세한 솔루션을 제공하세요.`;
const response = await model.invoke([new HumanMessage(prompt)]);
return {
current_solution: response.content as string,
iteration: state.iteration + 1,
};
}
// 평가자: 솔루션 비판
async function evaluateSolution(
state: typeof ReflectionState.State
): Promise<Partial<typeof ReflectionState.State>> {
const critiquePrompt = `이 솔루션을 비판적으로 평가하세요:
문제: ${state.problem}
솔루션: ${state.current_solution}
제공할 내용:
1. 약점을 식별하는 자세한 비판
2. 개선을 위한 구체적인 제안
3. 0-10점 점수
JSON 형식으로: {
"critique": "...",
"suggestions": ["...", "..."],
"score": 0
}`;
const response = await model.invoke([new HumanMessage(critiquePrompt)]);
try {
const evaluation = JSON.parse(response.content as string);
return {
critiques: [evaluation],
};
} catch {
// JSON 파싱 실패 시 폴백
return {
critiques: [{
critique: response.content as string,
suggestions: [],
score: 5,
}],
};
}
}
// 솔루션이 충분히 좋은지 확인
function shouldContinueReflection(
state: typeof ReflectionState.State
): "reflect" | "finalize" {
const MAX_ITERATIONS = 3;
const GOOD_SCORE = 8;
const lastCritique = last(state.critiques);
if (state.iteration >= MAX_ITERATIONS) {
return "finalize";
}
if (lastCritique && lastCritique.score >= GOOD_SCORE) {
return "finalize";
}
return "reflect";
}
// 최고의 솔루션 최종화
async function finalizeSolution(
state: typeof ReflectionState.State
): Promise<Partial<typeof ReflectionState.State>> {
return {
final_solution: state.current_solution,
};
}
// Self-Reflection 워크플로우 구축
export function createReflectionWorkflow() {
const workflow = new StateGraph(ReflectionState)
.addNode("generate", generateSolution)
.addNode("evaluate", evaluateSolution)
.addNode("finalize", finalizeSolution)
.addEdge("__start__", "generate")
.addEdge("generate", "evaluate")
.addConditionalEdges(
"evaluate",
shouldContinueReflection,
{
reflect: "generate",
finalize: "finalize",
}
)
.addEdge("finalize", "__end__");
return workflow.compile();
}
Self-Reflection은 비판과 개선 사이클을 통해 솔루션을 반복적으로 개선합니다.
3. 여러 추론 패턴 오케스트레이션
// app/agents/advanced-reasoning/orchestrator.ts
import { StateGraph, Annotation, Send } from "@langchain/langgraph";
import { createToTWorkflow } from "./tree-of-thought";
import { createReflectionWorkflow } from "./self-reflection";
import { ChatGoogleGenerativeAI } from "@langchain/google-genai";
import { HumanMessage } from "@langchain/core/messages";
import { z } from "zod";
import { maxBy, map } from "es-toolkit";
// 마스터 오케스트레이터 상태
const OrchestratorState = Annotation.Root({
problem: Annotation<string>(),
problem_type: Annotation<string>({
reducer: (_, y) => y,
default: () => "unknown",
}),
tot_result: Annotation<{
solution: string;
paths: string[][];
}>(),
reflection_result: Annotation<{
solution: string;
iterations: number;
}>(),
final_answer: Annotation<string>({
reducer: (_, y) => y,
default: () => "",
}),
reasoning_method: Annotation<string>({
reducer: (_, y) => y,
default: () => "",
}),
});
const model = new ChatGoogleGenerativeAI({
modelName: "gemini-pro",
temperature: 0.3,
});
// 추론 전략을 선택하기 위해 문제 분류
async function classifyProblem(
state: typeof OrchestratorState.State
): Promise<Partial<typeof OrchestratorState.State>> {
const classificationPrompt = `최적의 추론 접근법을 결정하기 위해 이 문제를 분류하세요:
문제: ${state.problem}
카테고리:
- "exploratory": 여러 솔루션 경로가 필요한 문제 (Tree-of-Thought 사용)
- "refinement": 반복적 개선이 필요한 문제 (Self-Reflection 사용)
- "hybrid": 탐색과 개선 모두 필요한 복잡한 문제
카테고리 이름만 반환하세요.`;
const response = await model.invoke([new HumanMessage(classificationPrompt)]);
const problemType = (response.content as string).toLowerCase().trim();
return {
problem_type: problemType,
};
}
// Tree-of-Thought 추론 실행
async function runToT(
state: typeof OrchestratorState.State
): Promise<Partial<typeof OrchestratorState.State>> {
const totWorkflow = createToTWorkflow();
const result = await totWorkflow.invoke({
problem: state.problem,
thoughts: [],
current_depth: 0,
});
return {
tot_result: {
solution: result.best_solution,
paths: result.exploration_paths,
},
reasoning_method: "Tree-of-Thought",
};
}
// Self-Reflection 추론 실행
async function runReflection(
state: typeof OrchestratorState.State
): Promise<Partial<typeof OrchestratorState.State>> {
const reflectionWorkflow = createReflectionWorkflow();
const result = await reflectionWorkflow.invoke({
problem: state.problem,
current_solution: "",
critiques: [],
iteration: 0,
});
return {
reflection_result: {
solution: result.final_solution,
iterations: result.iteration,
},
reasoning_method: "Self-Reflection",
};
}
// 여러 추론 방법의 결과 통합
async function synthesizeResults(
state: typeof OrchestratorState.State
): Promise<Partial<typeof OrchestratorState.State>> {
let solutions = [];
if (state.tot_result) {
solutions.push({
method: "Tree-of-Thought",
solution: state.tot_result.solution,
});
}
if (state.reflection_result) {
solutions.push({
method: "Self-Reflection",
solution: state.reflection_result.solution,
});
}
if (solutions.length === 1) {
return {
final_answer: solutions[0].solution,
};
}
// 여러 솔루션이 있는 경우 통합
const synthesisPrompt = `이러한 솔루션을 포괄적인 답변으로 통합하세요:
문제: ${state.problem}
${map(solutions, s => `${s.method} 솔루션:\n${s.solution}`).join('\n\n')}
각 접근법의 최고의 통찰력을 결합한 최종 답변을 작성하세요.`;
const response = await model.invoke([new HumanMessage(synthesisPrompt)]);
return {
final_answer: response.content as string,
reasoning_method: "하이브리드 (ToT + Self-Reflection)",
};
}
// 문제 유형에 따른 라우팅
function routeReasoning(state: typeof OrchestratorState.State): string | string[] {
switch (state.problem_type) {
case "exploratory":
return "tot";
case "refinement":
return "reflection";
case "hybrid":
return ["tot", "reflection"];
default:
return "tot"; // 기본 폴백
}
}
// 마스터 오케스트레이터 생성
export function createReasoningOrchestrator() {
const workflow = new StateGraph(OrchestratorState)
.addNode("classify", classifyProblem)
.addNode("tot", runToT)
.addNode("reflection", runReflection)
.addNode("synthesize", synthesizeResults)
.addEdge("__start__", "classify")
.addConditionalEdges(
"classify",
routeReasoning,
{
tot: "tot",
reflection: "reflection",
}
)
.addEdge("tot", "synthesize")
.addEdge("reflection", "synthesize")
.addEdge("synthesize", "__end__");
return workflow.compile();
}
// 스트리밍이 있는 API 라우트 핸들러
export async function POST(request: Request) {
const { problem } = await request.json();
const orchestrator = createReasoningOrchestrator();
// 스트리밍 응답 생성
const encoder = new TextEncoder();
const stream = new ReadableStream({
async start(controller) {
try {
// 이벤트가 발생하면 스트리밍
for await (const event of orchestrator.streamEvents(
{ problem },
{ version: "v2" }
)) {
if (event.event === "on_chain_stream") {
const chunk = encoder.encode(
`data: ${JSON.stringify({
type: "progress",
node: event.name,
data: event.data,
})}\n\n`
);
controller.enqueue(chunk);
}
}
// 최종 결과 가져오기
const result = await orchestrator.invoke({ problem });
const finalChunk = encoder.encode(
`data: ${JSON.stringify({
type: "complete",
answer: result.final_answer,
method: result.reasoning_method,
tot_result: result.tot_result,
reflection_result: result.reflection_result,
})}\n\n`
);
controller.enqueue(finalChunk);
controller.close();
} catch (error) {
controller.error(error);
}
},
});
return new Response(stream, {
headers: {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
},
});
}
오케스트레이터는 문제 특성에 따라 추론 전략을 지능적으로 선택하고 결합합니다.
4. 실시간 스트리밍을 통한 프론트엔드
// app/components/AdvancedReasoningAgent.tsx
'use client';
import { useState, useCallback } from 'react';
import { useMutation } from '@tanstack/react-query';
import { map, groupBy } from 'es-toolkit';
interface StreamEvent {
type: 'progress' | 'complete';
node?: string;
data?: any;
answer?: string;
method?: string;
tot_result?: any;
reflection_result?: any;
}
export function AdvancedReasoningAgent() {
const [problem, setProblem] = useState('');
const [events, setEvents] = useState<StreamEvent[]>([]);
const [isStreaming, setIsStreaming] = useState(false);
const startReasoning = useCallback(async () => {
setIsStreaming(true);
setEvents([]);
try {
const response = await fetch('/agents/advanced-reasoning/orchestrator', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ problem }),
});
const reader = response.body?.getReader();
const decoder = new TextDecoder();
if (!reader) throw new Error('리더를 사용할 수 없습니다');
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
setEvents(prev => [...prev, data]);
}
}
}
} catch (error) {
console.error('스트리밍 오류:', error);
} finally {
setIsStreaming(false);
}
}, [problem]);
const completeEvent = events.find(e => e.type === 'complete');
const progressEvents = events.filter(e => e.type === 'progress');
return (
<div className="max-w-4xl mx-auto p-6 space-y-6">
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h2 className="card-title">고급 멀티 에이전트 추론</h2>
<textarea
className="textarea textarea-bordered h-32"
placeholder="깊은 추론이 필요한 복잡한 문제를 입력하세요..."
value={problem}
onChange={(e) => setProblem(e.target.value)}
/>
<button
className={`btn btn-primary ${isStreaming ? 'loading' : ''}`}
onClick={startReasoning}
disabled={isStreaming || !problem}
>
{isStreaming ? '추론 진행 중...' : '고급 추론 시작'}
</button>
</div>
</div>
{progressEvents.length > 0 && (
<div className="card bg-base-200">
<div className="card-body">
<h3 className="font-semibold">추론 진행 상황</h3>
<div className="space-y-2">
{map(progressEvents, (event, idx) => (
<div key={idx} className="flex items-center gap-2">
<div className="badge badge-primary">{event.node}</div>
<span className="text-sm opacity-70">처리 중...</span>
</div>
))}
</div>
</div>
</div>
)}
{completeEvent && (
<div className="space-y-4">
<div className="card bg-success text-success-content">
<div className="card-body">
<h3 className="card-title">최종 답변</h3>
<p className="whitespace-pre-wrap">{completeEvent.answer}</p>
<div className="card-actions justify-end">
<div className="badge badge-outline">
방법: {completeEvent.method}
</div>
</div>
</div>
</div>
{completeEvent.tot_result && (
<div className="card bg-base-100">
<div className="card-body">
<h4 className="font-semibold">Tree-of-Thought 탐색</h4>
<div className="text-sm space-y-1">
{map(completeEvent.tot_result.paths[0], (step, idx) => (
<div key={idx} className="pl-4 border-l-2 border-primary">
단계 {idx + 1}: {step}
</div>
))}
</div>
</div>
</div>
)}
{completeEvent.reflection_result && (
<div className="card bg-base-100">
<div className="card-body">
<h4 className="font-semibold">Self-Reflection 프로세스</h4>
<p className="text-sm opacity-70">
{completeEvent.reflection_result.iterations}회 반복을 통해 개선됨
</p>
</div>
</div>
)}
</div>
)}
</div>
);
}
프론트엔드는 점진적 업데이트와 함께 추론 프로세스에 대한 실시간 피드백을 제공합니다.
성능 최적화
// app/lib/reasoning-cache.ts
import { LRUCache } from 'lru-cache';
import { createHash } from 'crypto';
// 추론 결과를 위한 시맨틱 캐싱
const reasoningCache = new LRUCache<string, any>({
max: 100,
ttl: 1000 * 60 * 60, // 1시간
updateAgeOnGet: true,
});
export function getCacheKey(problem: string, method: string): string {
return createHash('sha256')
.update(`${problem}-${method}`)
.digest('hex');
}
export async function withCache<T>(
key: string,
fn: () => Promise<T>
): Promise<T> {
const cached = reasoningCache.get(key);
if (cached) {
console.log(`키에 대한 캐시 히트: ${key}`);
return cached;
}
const result = await fn();
reasoningCache.set(key, result);
return result;
}
// 병렬 처리 헬퍼
export async function parallelReasoning<T>(
tasks: Array<() => Promise<T>>,
maxConcurrency: number = 3
): Promise<T[]> {
const results: T[] = [];
const executing: Promise<void>[] = [];
for (const task of tasks) {
const promise = task().then(result => {
results.push(result);
});
executing.push(promise);
if (executing.length >= maxConcurrency) {
await Promise.race(executing);
executing.splice(executing.findIndex(p => p === promise), 1);
}
}
await Promise.all(executing);
return results;
}
캐싱과 병렬 처리는 지연 시간과 비용을 크게 줄입니다.
결론
추론 기술은 에이전트를 단순한 응답자에서 지능적인 문제 해결자로 변환합니다. 선형 문제에는 기본 Chain-of-Thought로 시작한 다음, 탐색을 위한 Tree-of-Thought와 개선을 위한 Self-Reflection을 점진적으로 추가합니다. 오케스트레이터 패턴은 문제 특성에 따라 추론 전략을 동적으로 선택할 수 있게 합니다. 프로덕션 성능을 위해 캐싱, 스트리밍 및 병렬 처리를 구현하는 것을 잊지 마세요. 이러한 패턴은 LangGraph의 상태 관리 및 TypeScript의 타입 안전성과 결합되어 서버리스 환경에서 복잡한 실제 문제를 해결할 수 있는 강력한 추론 시스템을 만듭니다.