AI/agent

Reflection Agent (CLI → API) + React 구현 가이드

hmmmmmmmmmmmm 2025. 6. 15. 22:09

 

프로젝트 개요

기존 CLI 기반의 Reflection Agent를 웹 애플리케이션으로 전환하는 프로젝트

LangGraph를 활용한 AI 트윗 생성 및 자체 개선 시스템을 FastAPI 백엔드와 React 프론트엔드로 구현하여,

사용자가 웹 브라우저에서 직관적으로 바이럴 트윗을 생성하고,

개선 과정을 실시간으로 확인

 

 

시스템 아키텍처

 

전체 흐름

1. 사용자 입력 - React 프론트엔드에서 트윗 주제 입력

2. 초기 트윗 생성 - AI가 첫 번째 트윗 생성

3. 자체 분석 - AI가 생성한 트윗을 스스로 분석하고 개선점 도출

4. 트윗 개선 - 분석 결과를 바탕으로 트윗 재작성

5. 반복 개선 - 최대 2회까지 분석-개선 사이클 실행

6. 결과 출력 - 각 단계별 결과를 실시간으로 화면에 표시

 

 

컴포넌트 구조

1. React Frontend (Port 3000): 사용자 인터페이스 및 결과 표시

2. FastAPI Backend (Port 8000): REST API 서버 및 LangGraph 실행

3. LangGraph Agent: 트윗 생성 및 반성 워크플로우 관리

 

 

FastAPI 백엔드 구현

 

1. 기본 서버 설정 및 CORS 구성

from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# React 앱과의 통신을 위한 CORS 설정
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 개발용 전체 허용
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

 

CORS 미들웨어는 브라우저의 동일 출처 정책(Same-Origin Policy)을 우회하여

React 앱(localhost:3000)에서 FastAPI 서버(localhost:8000)로의 API 호출을 허용

 

 

 

2. LangGraph 노드 구현

 

Generation Node (트윗 생성)

def generation_node(state: Sequence[BaseMessage]):
    """트윗 생성 노드"""
    result = generate_chain.invoke({"messages": state})
    print(f"\n[DEBUG] generate_chain result: {type(result)}\n")
    
    # 타입 안전성 보장 - 예상치 못한 응답 타입 처리
    if not isinstance(result, BaseMessage):
        result = AIMessage(content=str(result))
    return list(state) + [result]

 

generate_chain을 호출하여 트윗을 생성하고,

결과가 BaseMessage 타입이 아닐 경우 AIMessage로 변환하여 타입 안전성을 보장

 

 

Reflection Node (자체 분석)

def reflection_node(messages: Sequence[BaseMessage]) -> List[BaseMessage]:
    """트윗 분석 및 개선 제안 노드"""
    # 최신 AI 메시지를 역순으로 탐색
    last_ai_message = None
    for msg in reversed(messages):
        if isinstance(msg, AIMessage):
            last_ai_message = msg
            break
    
    if last_ai_message:
        analysis_messages = [HumanMessage(content=f"""
            다음 트윗을 분석하고 구체적인 개선점을 찾아주세요:
            트윗: "{last_ai_message.content}"
            
            다음 관점에서 평가하고 개선안을 제시해주세요:
            1. 길이 (280자 이내 최적화)
            2. 감정적 임팩트 (호기심, 공감, 놀라움 등)
            3. 행동 유도 (리트윗, 댓글을 유도하는 요소)
            4. 트렌드 반영 (최신 트렌드, 밈, 표현)
            5. 가독성 (이모지, 줄바꿈 등)
        """)]
    
    res = reflect_chain.invoke({"messages": analysis_messages})
    return list(messages) + [HumanMessage(content=res.content)]

 

메시지 목록을 역순으로 순회하여 가장 최근의 AI 생성 트윗을 찾고,

5가지 관점에서 구체적인 분석과 개선안을 제시

 

 

3. LangGraph 워크플로우 구성

builder = MessageGraph()
builder.add_node(GENERATE, generation_node)
builder.add_node(REFLECT, reflection_node)
builder.set_entry_point(GENERATE)

def should_continue(state: List[BaseMessage]):
    """종료 조건: 3회 반복 후 종료 (초기 요청 + 2번의 개선)"""
    return END if len(state) > 5 else REFLECT

builder.add_conditional_edges(GENERATE, should_continue)
builder.add_edge(REFLECT, GENERATE)

graph = builder.compile()

 

GENERATE 노드에서 시작

should_continue 함수로 메시지가 5개를 초과하면 종료, 아니면 REFLECT로 이동

REFLECT에서는 항상 GENERATE로 돌아가는 순환 구조

 

 

4. API 엔드포인트

@app.post("/generate")
async def generate(request: Request):
    data = await request.json()
    input_text = data.get("text", "")
    result = graph.invoke(HumanMessage(content=input_text))
    return {
        "results": [
            {"type": msg.type, "content": msg.content}
            for msg in result
        ]
    }

 

사용자의 입력을 받아 LangGraph를 실행하고, 모든 메시지를 JSON 형태로 반환

 

 

 

React 프론트엔드 구현

 

1. 상태 관리 및 API 호출

const [inputText, setInputText] = useState('');
const [results, setResults] = useState([]);
const [isLoading, setIsLoading] = useState(false);

const handleSubmit = async (e) => {
    e.preventDefault();
    setIsLoading(true);
    
    try {
        const response = await axios.post(`${API_BASE_URL}/generate`, {
            text: inputText
        });
        setResults(response.data.results);
    } catch (err) {
        console.error('API Error:', err);
    } finally {
        setIsLoading(false);
    }
};

 

React Hooks를 사용하여 상태를 관리하고, axios로 FastAPI 서버와 통신

 

 

2. 단계별 결과 표시

const getStepLabel = (index, type) => {
    if (index === 0) return '📝 초기 입력';
    if (type === 'ai') {
        const aiIndex = Math.floor(index / 2);
        return `🤖 AI 트윗 생성 ${aiIndex > 1 ? `(${aiIndex}차 개선)` : ''}`;
    } else {
        const analysisIndex = Math.floor((index + 1) / 2);
        return `🔍 AI 분석 ${analysisIndex}`;
    }
};

 

메시지의 인덱스와 타입을 기반으로 각 단계에 적절한 라벨을 생성

 

 

3. 글자수 체크 및 복사 기능

const getCharacterCountColor = (length) => {
    if (length <= 250) return 'text-green-600';
    if (length <= 280) return 'text-orange-600';
    return 'text-red-600';
};

const copyToClipboard = (text) => {
    navigator.clipboard.writeText(text);
    // 토스트 알림 표시
    showToast('클립보드에 복사되었습니다!');
};

 

트위터의 280자 제한에 맞춰 글자수를 색상으로 표시하고,

클립보드 API를 사용하여 원클릭 복사 기능을 구현

 

 

4. 반응형 UI 컴포넌트

return (
    <div className="space-y-6">
        {results.map((message, index) => (
            <div key={index} className={`bg-white rounded-lg shadow-lg p-6 ${
                message.type === 'ai' ? 'border-l-4 border-blue-500' : 'border-l-4 border-green-500'
            }`}>
                <div className="flex items-start gap-3">
                    {getMessageIcon(message.type)}
                    <div className="flex-1">
                        <h3 className="font-medium text-gray-800 mb-2">
                            {getStepLabel(index, message.type)}
                        </h3>
                        <div className="bg-gray-50 rounded-lg p-4">
                            <p className="text-gray-700 whitespace-pre-wrap leading-relaxed">
                                {message.content}
                            </p>
                        </div>
                        {message.type === 'ai' && (
                            <div className="mt-3 flex items-center justify-between">
                                <span className={`text-sm font-medium ${getCharacterCountColor(message.content.length)}`}>
                                    {message.content.length}/280자
                                </span>
                                <button onClick={() => copyToClipboard(message.content)}
                                        className="flex items-center gap-1 px-3 py-1 bg-blue-500 text-white rounded">
                                    <Copy className="w-4 h-4" />
                                    복사
                                </button>
                            </div>
                        )}
                    </div>
                </div>
            </div>
        ))}
    </div>
);

 

Tailwind CSS를 사용하여 반응형 카드 레이아웃을 구현하고,

메시지 타입에 따라 다른 색상의 보더를 적용

 

 

 

워크플로우 제어 로직

 

종료 조건 최적화

def should_continue(state: List[BaseMessage]):
    return END if len(state) > 5 else REFLECT

 

총 메시지 수가 5개를 초과하면 종료하여 적절한 개선 횟수(초기 요청 + 2라운드)를 보장

모든 노드에서 BaseMessage 타입을 체크하고, 예상치 못한 응답을 AIMessage로 변환하여 워크플로우의 안정성을 확보

 

 

주요 특징

1. 자체 반성 시스템: AI가 생성한 트윗을 5가지 관점(길이, 감정적 임팩트, 행동 유도, 트렌드 반영, 가독성)에서 스스로 분석하고 개선하는 메타인지 능력 구현

2. 실시간 워크플로우 시각화: 각 단계별 메시지를 순차적으로 표시하여 AI의 사고 과정을 투명하게 조회

3. 확장 가능한 아키텍처: FastAPI와 React의 분리된 구조로 독립적인 확장 및 배포가 가능하며, RESTful API를 통해 다른 클라이언트와도 쉽게 통합

 

 

동작 화면

1. 사용자가 트윗 내용 입력

 

 

2. 개선 결과

 

 

https://github.com/myjhye/reflection-agent

 

GitHub - myjhye/reflection-agent

Contribute to myjhye/reflection-agent development by creating an account on GitHub.

github.com