PHASE 05 · 상세판

Agent 프레임워크와 엔지니어링

AI가 스스로 복잡한 작업을 완수하게 하기 + 애플리케이션 배포하기

총 기간: 2~4주
일일 투자: 2~3시간
업무와 병행 가능

Phase 5는 "쓸 수 있는" 수준에서 "잘 쓸 수 있는" 수준으로 만들어 줍니다

Phase 1-4에서 API 호출, Prompt 작성, RAG 구현을 배웠습니다. 하지만 실무에서는 더 많은 것이 필요합니다: AI가 스스로 계획을 세우고 여러 단계의 작업을 완수하게 하기(Agent), 데모 가능한 인터페이스 빠르게 구축하기(Streamlit/Gradio), 서비스 배포하기(FastAPI/Docker), AI 출력 품질을 지속적으로 평가하고 최적화하기(Eval).

이 단계는 "데모를 만들 수 있는" 수준에서 "제품을 납품할 수 있는" 수준으로 진화시켜 줍니다. 완료하면 AI 프로젝트를 독립적으로 담당할 수 있는 풀스택 역량을 갖추게 됩니다.

🤔
사고 Think
작업 이해, 계획 수립
🛠️
행동 Act
도구 호출, 작업 실행
👀
관찰 Observe
결과 확인, 상태 판단
결정 Decide
계속 or 완료

↩ 미완료 시 「사고」로 돌아가 계속 실행 — 이것이 Agent Loop입니다

📋 학습 일정 목차

DAY1-3
AI Agent 핵심 원리와 직접 구현
프레임워크 없이 먼저——Agent가 어떻게 "스스로 사고"하는지 처음부터 이해하기
🧠 Agent vs 일반 API 호출 필수
일반 API 호출AI Agent
프로세스모든 단계를 직접 코딩AI가 스스로 실행할 내용을 결정
도구직접 호출AI가 자동으로 어떤 도구를 호출할지 선택
루프한 번 호출하면 끝작업이 완료될 때까지 반복 실행
오류 처리직접 예외 처리AI가 오류를 관찰한 후 스스로 전략 조정
비유리모컨 자동차자율주행
🔧 처음부터 직접 ReAct Agent 만들기 필수

ReAct(Reasoning + Acting)는 가장 클래식한 Agent 패턴입니다. AI가 먼저 추론(Thought)하고, 행동(Action)한 다음, 결과를 관찰(Observation)하며, 완료될 때까지 반복 실행합니다.

react_agent.py — 직접 만드는 ReAct Agent
import json, re
from openai import OpenAI
from datetime import datetime

client = OpenAI()

# ========== 1. 도구 정의 ==========
def search_web(query):
    """웹 검색 시뮬레이션"""
    fake_results = {
        "Python": "Python은 고급 프로그래밍 언어이며, 최신 버전은 3.12입니다.",
        "RAG": "RAG는 검색 증강 생성으로, 2024-2025년 가장 인기 있는 AI 기술 중 하나입니다.",
    }
    for key, val in fake_results.items():
        if key.lower() in query.lower():
            return val
    return f"'{query}'에 대한 검색 결과를 찾을 수 없습니다"

def calculator(expression):
    """수학 표현식 안전 계산"""
    try:
        return str(eval(expression, {"__builtins__": {}}, {}))
    except:
        return "계산 오류"

def get_current_time():
    """현재 시간 가져오기"""
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

TOOLS = {
    "search_web": {"func": search_web, "desc": "웹 정보 검색, 매개변수: query"},
    "calculator": {"func": calculator, "desc": "수학 표현식 계산, 매개변수: expression"},
    "get_current_time": {"func": get_current_time, "desc": "현재 시간 가져오기, 매개변수 없음"},
}

# ========== 2. Agent 시스템 Prompt ==========
AGENT_PROMPT = """당신은 도구를 사용하여 작업을 완수할 수 있는 지능형 어시스턴트입니다.

사용 가능한 도구:
{tool_descriptions}

작업 흐름:
1. 사용자 요구 분석
2. 필요시 도구를 호출하여 정보 획득
3. 결과를 바탕으로 질문에 답변

도구 호출 시, 다음 JSON 형식을 엄격히 사용하세요 (한 줄로 작성):
TOOL_CALL: {{"tool": "도구명", "args": {{"매개변수명": "매개변수값"}}}}

도구가 필요 없으면 바로 답변하세요.
작업이 완료되면 FINAL_ANSWER: 로 시작하는 최종 답변을 제공하세요."""

# ========== 3. Agent 루프 ==========
class SimpleAgent:
    def __init__(self, tools, max_steps=5):
        self.tools = tools
        self.max_steps = max_steps

        tool_desc = "\n".join(
            [f"- {name}: {t['desc']}" for name, t in tools.items()]
        )
        self.system_prompt = AGENT_PROMPT.format(tool_descriptions=tool_desc)

    def run(self, task):
        messages = [
            {"role": "system", "content": self.system_prompt},
            {"role": "user", "content": task}
        ]

        for step in range(self.max_steps):
            print(f"\n--- Step {step + 1} ---")

            response = client.chat.completions.create(
                model="gpt-4o", messages=messages, temperature=0.3
            )
            reply = response.choices[0].message.content
            print(f"AI: {reply}")
            messages.append({"role": "assistant", "content": reply})

            # 최종 답변 확인
            if "FINAL_ANSWER:" in reply:
                return reply.split("FINAL_ANSWER:")[1].strip()

            # 도구 호출 확인
            if "TOOL_CALL:" in reply:
                tool_json = reply.split("TOOL_CALL:")[1].strip()
                try:
                    call = json.loads(tool_json)
                    tool_name = call["tool"]
                    tool_args = call.get("args", {})

                    print(f"🔧 도구 호출: {tool_name}({tool_args})")
                    result = self.tools[tool_name]["func"](**tool_args)
                    print(f"📋 결과: {result}")

                    messages.append({
                        "role": "user",
                        "content": f"도구 {tool_name}의 실행 결과: {result}"
                    })
                except Exception as e:
                    messages.append({
                        "role": "user", "content": f"도구 호출 실패: {e}"
                    })

        return "최대 단계 수에 도달, 작업 미완료"

# ========== 사용 ==========
agent = SimpleAgent(TOOLS)
answer = agent.run("지금 몇 시야? 그리고 1024 * 768이 얼마인지 계산해줘")
print(f"\n최종 답변: {answer}")

📌 Agent의 핵심은 하나의 While 루프입니다

  • ① LLM이 사고하고, 다음에 할 일을 결정
  • ② 도구가 필요하면 → 도구 호출 → 결과를 LLM에 다시 전달
  • ③ 도구가 필요 없으면 → 최종 답변 출력 → 루프 종료
  • ④ max_steps 설정으로 무한 루프 방지

🏋️ Day 1-3 연습

Agent에 "파일 읽기" 도구를 추가하여 로컬 txt 파일을 읽고 질문에 답할 수 있게 하기
Agent에 RAG 도구 추가: Phase 4의 지식 베이스 검색 기능 연동
복잡한 작업에 대한 Agent 처리 테스트: 2-3개의 도구를 호출해야 완료되는 문제
DAY4-6
LangChain Agent 실전
프레임워크로 강력한 Agent를 빠르게 구축——바퀴를 다시 발명할 필요 없이
🦜 LangChain Agent 빠른 구축 필수
TERMINAL
pip install langchain langchain-openai langchain-community duckduckgo-search
langchain_agent.py
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool

# ---- 1. 도구 정의 (데코레이터 사용, 매우 간단) ----
@tool
def calculator(expression: str) -> str:
    """수학 표현식을 계산합니다. '2 + 3 * 4'와 같은 수학 표현식 문자열을 입력합니다"""
    try:
        return str(eval(expression, {"__builtins__": {}}, {}))
    except:
        return "계산 오류"

@tool
def get_word_count(text: str) -> str:
    """텍스트의 단어 수와 문자 수를 집계합니다"""
    words = len(text.split())
    chars = len(text)
    return f"단어 수: {words}, 문자 수: {chars}"

tools = [calculator, get_word_count]

# ---- 2. Agent 생성 ----
llm = ChatOpenAI(model="gpt-4o", temperature=0.3)
prompt = ChatPromptTemplate.from_messages([
    ("system", "당신은 도구를 사용하여 작업을 완수할 수 있는 유능한 AI 어시스턴트입니다."),
    ("human", "{input}"),
    ("placeholder", "{agent_scratchpad}"),
])

agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

# ---- 3. 실행 ----
result = executor.invoke({"input": "(125 * 37) + (89 * 43)의 결과를 계산해주세요"})
print(result["output"])
🔗 RAG + Agent 결합 (최강 조합) 필수

RAG 지식 베이스 검색을 하나의 도구로 캡슐화하면, Agent가 자동으로 "언제 지식 베이스를 조회해야 하는지" 판단할 수 있습니다.

rag_agent.py
@tool
def search_knowledge_base(query: str) -> str:
    """기업 지식 베이스에서 관련 정보를 검색합니다. 사용자가 제품, 정책, 프로세스 관련 질문을 할 때 사용합니다."""
    # Phase 4의 검색 로직 재사용
    emb = get_embeddings([query])[0]
    results = collection.query(query_embeddings=[emb], n_results=3)
    if results["documents"][0]:
        return "\n---\n".join(results["documents"][0])
    return "지식 베이스에서 관련 정보를 찾을 수 없습니다"

# Agent가 동시에 보유: 지식 베이스 검색 + 계산기 + 기타 도구
tools = [search_knowledge_base, calculator, get_word_count]
# AI가 자동 판단: 사용자가 제품 질문 → 지식 베이스 조회, 수학 질문 → 계산기 사용

🏋️ Day 4-6 연습

LangChain으로 최소 3개의 도구(검색, 계산, 지식 베이스)를 포함하는 Agent 구축
Agent에게 복잡한 작업 완수시키기: "XXX 제품의 가격을 조회하고, 5개 구매 시 총 비용을 계산해줘"
verbose=True를 활성화하여 Agent의 사고 과정을 관찰하고, 각 단계의 결정을 이해하기
DAY7-9
Web 인터페이스: Streamlit / Gradio
AI 애플리케이션에 "얼굴"을 달아주기——10분 만에 데모 가능한 Web 인터페이스 구축
🎨 Streamlit — Python 세계에서 가장 빠른 Web 프레임워크 필수
TERMINAL
pip install streamlit
app.py — RAG 지식 베이스 Q&A 인터페이스
import streamlit as st
from openai import OpenAI

st.set_page_config(page_title="AI 知识库问答", page_icon="🧠")
st.title("🧠 企业知识库问答系统")

# ---- 사이드바: 파일 업로드 ----
with st.sidebar:
    st.header("📁 文档管理")
    uploaded = st.file_uploader("문서 업로드", type=["pdf", "txt"], accept_multiple_files=True)
    if uploaded:
        st.success(f"{len(uploaded)}개 파일 업로드 완료")
        # 여기서 입고 로직 호출...

# ---- 채팅 인터페이스 ----
if "messages" not in st.session_state:
    st.session_state.messages = []

# 이전 메시지 표시
for msg in st.session_state.messages:
    with st.chat_message(msg["role"]):
        st.markdown(msg["content"])

# 입력창
if question := st.chat_input("질문을 입력하세요..."):
    # 사용자 메시지 표시
    st.session_state.messages.append({"role": "user", "content": question})
    with st.chat_message("user"):
        st.markdown(question)

    # AI 답변 표시
    with st.chat_message("assistant"):
        with st.spinner("생각 중..."):
            # RAG Q&A 함수 호출
            result = rag_query(question)  # Phase 4의 함수
            st.markdown(result["answer"])
            if result["sources"]:
                st.caption(f"📎 출처: {', '.join(result['sources'])}")

    st.session_state.messages.append({"role": "assistant", "content": result["answer"]})
TERMINAL — 실행
streamlit run app.py    # 브라우저가 자동으로 http://localhost:8501 열기
🖼️ Gradio — AI 데모에 더 적합한 프레임워크 중요
gradio_app.py — 5줄로 채팅 인터페이스 완성
import gradio as gr

def chat_fn(message, history):
    result = rag_query(message)
    return result["answer"]

demo = gr.ChatInterface(fn=chat_fn, title="AI 知识库问答")
demo.launch()   # → http://localhost:7860

💡 Streamlit vs Gradio

Streamlit — 더 유연하며, 완전한 Web 애플리케이션 제작 가능 (대시보드, 폼, 멀티페이지)
Gradio — 더 빠르며, 몇 줄의 코드로 채팅 인터페이스 완성, 원클릭으로 공개 링크 공유 가능
권장: 프로토타입/데모는 Gradio, 완성된 제품은 Streamlit

🏋️ Day 7-9 연습

Streamlit으로 RAG Q&A 인터페이스 구축: 파일 업로드 + 채팅 + 출처 표시
Gradio로 동일한 기능을 구축하고, 개발 속도와 결과 비교
인터페이스에 token 소비 통계, 대화 내보내기 기능 추가
DAY10-12
API 서비스: FastAPI 배포
AI 기능을 API로 래핑——프론트엔드/다른 시스템에서 호출 가능하게
FastAPI로 AI API 빠르게 구축 필수
TERMINAL
pip install fastapi uvicorn
api_server.py
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel
from typing import List

app = FastAPI(title="AI 知识库 API")

# ---- 요청/응답 모델 ----
class QueryRequest(BaseModel):
    question: str
    top_k: int = 3

class QueryResponse(BaseModel):
    answer: str
    sources: List[str]
    tokens: int

# ---- API 라우트 ----
@app.post("/ask", response_model=QueryResponse)
async def ask_question(req: QueryRequest):
    try:
        result = rag_query(req.question, top_k=req.top_k)
        return QueryResponse(
            answer=result["answer"],
            sources=result["sources"],
            tokens=result.get("tokens", 0)
        )
    except Exception as e:
        raise HTTPException(status_code=500, detail=str(e))

@app.get("/health")
async def health():
    return {"status": "ok"}

# 실행: uvicorn api_server:app --reload
# 자동 문서: http://localhost:8000/docs

🏋️ Day 10-12 연습

FastAPI로 RAG Q&A를 래핑하고, /ask/upload 두 개의 엔드포인트 제공
requests 또는 curl로 API를 테스트하여 정상 작동 확인
/docs에 접속하여 자동 생성된 Swagger 문서 확인
DAY13-15
Eval 평가와 지속적 최적화
"AI가 잘 답하고 있는지 어떻게 알 수 있을까?"——데이터로 말하기
📏 평가 데이터셋 구축 필수
eval_dataset.py
import pandas as pd

# ---- 수동으로 평가 데이터셋 구축 ----
eval_data = [
    {"question": "반품 절차가 어떻게 되나요?",
     "expected_answer": "7일 이내 반품 가능, 반품 신청서 제출 필요",
     "expected_source": "반품교환정책.pdf"},
    {"question": "제품 보증 기간은 얼마나 되나요?",
     "expected_answer": "표준 보증 1년",
     "expected_source": "제품매뉴얼.pdf"},
    # ... 최소 20-50개
]
eval_df = pd.DataFrame(eval_data)

# ---- 일괄 평가 실행 ----
results = []
for _, row in eval_df.iterrows():
    result = rag_query(row["question"])
    results.append({
        "question": row["question"],
        "expected": row["expected_answer"],
        "actual": result["answer"],
        "sources": result["sources"],
        "source_hit": row["expected_source"] in result["sources"]
    })

results_df = pd.DataFrame(results)
print(f"출처 적중률: {results_df['source_hit'].mean():.1%}")
🤖 LLM-as-Judge (AI로 AI를 평가) 필수
llm_judge.py
JUDGE_PROMPT = """당신은 AI 답변 품질 평가관입니다. 아래 답변의 품질을 평가해 주세요.

질문: {question}
참고 답변: {expected}
실제 답변: {actual}

다음 차원에서 점수를 매겨주세요 (1-5):
- 정확성: 답변이 올바른가
- 완전성: 핵심 정보를 포함하고 있는가
- 간결성: 간결하고 명확한가

JSON 출력: {{"accuracy": X, "completeness": X, "conciseness": X, "total": X, "comment": "..."}}"""

def judge_answer(question, expected, actual):
    resp = client.chat.completions.create(
        model="gpt-4o",
        messages=[{"role": "user", "content": JUDGE_PROMPT.format(
            question=question, expected=expected, actual=actual
        )}],
        response_format={"type": "json_object"},
        temperature=0
    )
    return json.loads(resp.choices[0].message.content)

🏋️ Day 13-15 연습

30개 이상의 평가 데이터셋 구축
LLM-as-Judge로 일괄 채점하고, 평가 보고서 생성 (평균 점수, 최악의 case)
평가 결과를 바탕으로 Prompt 또는 chunk_size를 최적화하고, 재평가하여 개선 효과 비교
DAY16-18
Docker + Git + 엔지니어링 실습
프로젝트를 "배포 가능, 협업 가능, 재현 가능"하게 만들기
🐳 Docker 컨테이너화 필수
Dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
EXPOSE 8000
CMD ["uvicorn", "api_server:app", "--host", "0.0.0.0", "--port", "8000"]
TERMINAL
docker build -t my-ai-app .           # 이미지 빌드
docker run -p 8000:8000 my-ai-app     # 컨테이너 실행
📁 표준 프로젝트 구조 필수
권장 디렉토리 구조
my-ai-project/
├── app/
│   ├── main.py            # FastAPI 진입점
│   ├── rag.py             # RAG 핵심 로직
│   ├── agent.py           # Agent 로직
│   ├── tools.py           # 도구 정의
│   └── prompts.py         # Prompt 템플릿
├── data/
│   └── docs/              # 지식 베이스 문서
├── eval/
│   ├── dataset.json       # 평가 데이터
│   └── run_eval.py        # 평가 스크립트
├── streamlit_app.py       # Web 인터페이스
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── .env                   # API Key (Git에 커밋하지 않음)
├── .gitignore
└── README.md

🏋️ Day 16-18 연습

표준 구조로 프로젝트를 리팩토링하고, 코드를 모듈별로 분리
Dockerfile을 작성하고, Docker로 서비스를 시작할 수 있는지 확인
Git 저장소를 초기화하고, .gitignore 작성 (.env, __pycache__, chroma_db/ 제외)
DAY19-24
졸업 프로젝트: 풀스택 AI Agent 애플리케이션
포트폴리오의 핵심——완전하고 데모 가능한 AI 제품
🏆 프로젝트 요구사항: AI 리서치 어시스턴트 필수

문서를 업로드하여 지식 베이스를 구축하고, 웹 검색, 데이터 분석을 수행하며, 구조화된 보고서를 생성하는 AI 리서치 어시스턴트를 구축합니다. 이 프로젝트는 Phase 1-5의 모든 기술을 활용합니다.

📌 기능 목록

  • 문서 관리 — PDF/Word/TXT 업로드, 자동 분할, 벡터화, 저장
  • 지능형 Q&A — RAG 검색 + 대규모 모델 생성, 출처 인용 포함
  • Agent 능력 — 지식 베이스 조회, 웹 검색, 계산 시점을 자동 결정
  • 보고서 생성 — 주제 입력 시, Agent가 자동으로 정보를 수집하고 구조화된 보고서 생성
  • Web 인터페이스 — Streamlit 채팅 인터페이스 + 파일 업로드 + 보고서 다운로드
  • API 서비스 — FastAPI 백엔드, 외부 시스템 연동 지원
  • 평가 체계 — 평가 데이터셋 + 자동화 평가 스크립트
프로젝트 핵심 아키텍처 — research_agent.py
class ResearchAgent:
    """AI 리서치 어시스턴트 — RAG + Agent + 보고서 생성 통합"""

    def __init__(self):
        self.kb = KnowledgeBaseQA()       # Phase 4의 지식 베이스
        self.tools = self._init_tools()    # Agent 도구 모음
        self.cost_tracker = CostTracker()  # Phase 3의 비용 추적

    def _init_tools(self):
        return {
            "search_kb": self.kb.ask,           # 지식 베이스 검색
            "web_search": web_search,           # 웹 검색
            "calculator": calculator,            # 계산기
            "generate_report": self.gen_report, # 보고서 생성
        }

    def chat(self, user_input):
        """Agent 기반 채팅"""
        # Agent 루프: 사고 → 행동 → 관찰 → 결정
        ...

    def gen_report(self, topic):
        """연구 보고서 자동 생성"""
        # 1. 지식 베이스에서 관련 정보 검색
        # 2. 웹 검색으로 보충 정보 획득
        # 3. LLM으로 구조화된 보고서 통합
        # 4. Markdown 형식 보고서 반환
        ...

    def upload_docs(self, files):
        """문서 일괄 업로드"""
        for f in files:
            self.kb.add_document(f)

    def get_stats(self):
        """시스템 통계 정보"""
        return {
            "kb_chunks": self.kb.stats()["total_chunks"],
            "total_cost": self.cost_tracker.total_cost,
            "total_calls": self.cost_tracker.call_count,
        }

🏋️ 졸업 프로젝트 마일스톤

Day 19-20 — 프로젝트 골격 구축, RAG 핵심 기능, Agent 도구 모음
Day 21-22 — Streamlit Web 인터페이스 (채팅 + 파일 업로드 + 보고서 생성)
Day 23 — FastAPI 백엔드 + Docker 컨테이너화
Day 24 — 평가 체계 + 최적화 + README + GitHub에 푸시

🏁 Phase 5 통과 자가 진단 체크리스트

아래 모든 항목을 완료하면, AI 프로젝트를 독립적으로 납품할 수 있는 풀스택 역량을 갖추게 됩니다:

📚 추천 학습 자료

공식 문서LangChain Agents — python.langchain.com/docs/modules/agents
공식 문서Streamlit Docs — docs.streamlit.io
공식 문서FastAPI Docs — fastapi.tiangolo.com (한국어 버전 있음)
무료 강의Functions, Tools and Agents with LangChain — DeepLearning.AI
실전 튜토리얼Docker 입문 — docker-curriculum.com
도서Building LLM Apps — O'Reilly (2025 신간, 풀스택 AI 개발 커버)

🎓 5개 Phase 전체 완료!

완전 초보자에서 AI 프로젝트를 독립적으로 납품할 수 있는 풀스택 엔지니어까지, 완전한 학습 경로를 마쳤습니다.

Python → 데이터 처리 → 대규모 모델 API → RAG → Agent + 엔지니어링

지금 갖추게 된 역량: AI 애플리케이션 개발 엔지니어 / LLM 엔지니어 / AI 풀스택 엔지니어. 졸업 프로젝트를 GitHub에 푸시하고, 이력서에 기재하고, AI 커리어를 시작하세요!