본문 바로가기

AI

[AI] 에이전트를 만들어보자!

AI 에이전트가 뭘까?

요즘 AI 에이전트라는 말을 정말 많이 들으실 겁니다.

그냥 ChatGPT에게 질문하고 답변을 받는 걸 에이전트라고 부르는 걸까요? 공식 문서들을 보면 조금 더 명확한 구분이 있습니다.

OpenAI의 Agents SDK 문서에서는 에이전트를 다음 요소로 구성된 LLM이라고 설명합니다.

  • Instructions: 모델이 어떤 역할을 해야 하는지 알려주는 지시문
  • Model: 실제로 호출할 LLM
  • Tools: 에이전트가 작업을 수행하기 위해 호출할 수 있는 함수나 API
AI 에이전트 = LLM + 역할 지시문 + 사용할 수 있는 도구 + 작업 흐름

 

그럼 이제 이 에이전트를 한번 만들어보겠습니다. 다들 취업을 위해서나 이직을 위해서 이력서를 작성하실 테니 이력서를 작성하는 에이전트를 만들어볼까 합니다.

정석적인 순서대로 작성해보겠습니다.

 

Agent 만들기

1. 에이전트 목표 설정

내 기본 프로필, 업무 경험을 알 수 있는 프로젝트 데이터나 포트폴리오 내용을 읽고서 목표 직무에 맞는 내용만 정리해서 목표 직무에 맞는 이력서 초안을 작성한다. 작성된 결과를 md 파일 혹은 pdf 파일로 만든다.

 

다음으로는 기초부터 시작하기 위해 LangChain 같은 프레임워크 없이 Python만을 이용해서 작성해 보겠습니다. 우선은 심플하게 시작해 봅시다.

언어 모델 API는 예시에서는 OpenAI의 ChatGPT로 하겠습니다.

 

2.1 에이전트 구성 - Pure Python

## main.py
import os
from pathlib import Path

from dotenv import load_dotenv
from openai import OpenAI


ROOT_DIR = Path(__file__).parent
DATA_DIR = ROOT_DIR / "data"
OUTPUT_DIR = ROOT_DIR / "outputs"


class ResumeWriterAgent:
    """
    LangChain 없이 직접 만든 간단한 ResumeWriterAgent입니다.

    이 클래스는 다음 책임을 가집니다.
    1. 이력서 작성에 필요한 파일을 읽는다.
    2. 목표 직무에 맞는 프롬프트를 만든다.
    3. OpenAI API를 호출해서 이력서 초안을 생성한다.
    4. 생성된 초안을 파일로 저장한다.
    """

    def __init__(self, model: str):
        self.model = model
        self.client = OpenAI()

    def read_text(self, filename: str) -> str:
        """
        data 폴더 안의 markdown 파일을 읽습니다.
        """
        path = DATA_DIR / filename

        if not path.exists():
            raise FileNotFoundError(f"파일을 찾을 수 없습니다: {path}")

        return path.read_text(encoding="utf-8")

    def save_text(self, filename: str, content: str) -> Path:
        """
        outputs 폴더에 결과 markdown 파일을 저장합니다.
        """
        OUTPUT_DIR.mkdir(exist_ok=True)
        path = OUTPUT_DIR / filename
        path.write_text(content, encoding="utf-8")
        return path

    def build_prompt(self, profile: str, work_history: str, target_role: str) -> str:
        """
        모델에게 전달할 사용자 프롬프트를 구성합니다.
        """
        return f"""
아래 정보를 기반으로 목표 직무에 맞는 이력서 초안을 작성해줘.

주의할 점:
- 주어진 정보에 없는 회사명, 수치, 성과를 지어내지 않는다.
- 부족한 정보는 '추가로 확인할 내용'에 질문으로 정리한다.
- 이력서 문장은 업무와 성과 중심으로 작성한다.
- 포트폴리오에 옮길 수 있는 프로젝트 설명도 함께 작성한다.

[기본 프로필]
{profile}

[업무 경험]
{work_history}

[목표 직무]
{target_role}

출력 형식:

# 이력서 초안

## 한 줄 소개

## 핵심 역량

## 경력 요약

## 프로젝트 경험

## 포트폴리오 설명

## 추가로 확인할 내용
""".strip()

    def write_resume(self) -> str:
        """
        전체 작업을 실행합니다.
        """
        profile = self.read_text("profile.md")
        work_history = self.read_text("work_history.md")
        target_role = self.read_text("target_role.md")

        prompt = self.build_prompt(
            profile=profile,
            work_history=work_history,
            target_role=target_role,
        )

        response = self.client.responses.create(
            model=self.model,
            instructions="""
너는 이력서와 포트폴리오 작성을 도와주는 커리어 코치다.
개발자의 업무 경험을 목표 직무에 맞게 정리하되, 절대 과장하지 않는다.
""".strip(),
            input=prompt,
        )

        return response.output_text


def main():
    load_dotenv()

    model = os.getenv("OPENAI_MODEL", "gpt-5.4-mini")

    agent = ResumeWriterAgent(model=model)
    draft = agent.write_resume()

    output_path = agent.save_text("resume_draft_without_langchain.md", draft)

    print(f"이력서 초안 저장 완료: {output_path}")


if __name__ == "__main__":
    main()

2.2 코드 설명

코드 설명은 간단하게 중요한 부분이라고 생각되는 부분만 정리합니다.

2.2.1 입력 데이터 읽기

# profile.md = 기본 프로필
# work_history.md = 업무 경험
# target_role.md = 목표 직무

profile = self.read_text("profile.md")
work_history = self.read_text("work_history.md")
target_role = self.read_text("target_role.md")

이 부분은 이력서 작성에 필요한 재료를 읽는 단계입니다.

이번 예제에서는 세 가지 파일을 사용합니다.

2.2.2 프롬프트 만들기

# 프롬프트 만들기
prompt = self.build_prompt(
    profile=profile,
    work_history=work_history,
    target_role=target_role,
)

 

build_prompt는 읽어온 데이터를 하나의 요청 문장으로 합치는 역할을 합니다.

여기서 중요한 부분은 단순히 데이터를 붙이는 것이 아니라, 모델이 어떤 기준으로 작성해야 하는지도 함께 알려주는 것입니다.

2.2.3 instructions와 input 구분

response = self.client.responses.create(
    model=self.model,
    instructions="...",
    input=prompt,
)

# instructions = 에이전트의 고정 역할
# input = 사용자의 이번 요청과 데이터

instruction에는 모델이 단순히 문장을 요약하는 것이 아니라 커리어 코치 역할을 할 수 있게 지시를 담았습니다.

 

그리고 아직 LangChain을 사용하지 않았기 때문에 도구 선택이나 반복 실행 같은 기능은 직접 코드로 작성해야 합니다.

여기서는 아래 기능을 메서드로 분리했습니다.

  • read_text: 파일 읽기
  • save_text: 파일 저장
  • build_prompt: 프롬프트 생성
  • write_resume: 전체 실행

2.2.4 결과 저장하기

output_path = agent.save_text("resume_draft_without_langchain.md", draft)

API 호출 결과는 문자열로 돌아옵니다. 이 결과를 markdown 파일로 저장했습니다.

3. 에이전트 구성 - LangChain

3.1 LangChain 사용 방식으로 수정

LangChain을 사용하면 에이전트에게 도구를 등록할 수 있습니다.

기존에는 제가 직접 함수들을 만들고 직접 사용해서 플로우를 만들었다면 이제는 도구를 쥐어주고

에이전트가 직접 도구를 활용해서 만들도록 할 수 있습니다.

아래 도구를 만들어보겠습니다.

  • read_profile
  • read_work_history
  • read_target_role
  • save_resume_draft

그리고 에이전트에게 이렇게 요청합니다.

필요한 파일을 읽고, 목표 직무에 맞는 이력서 초안을 작성한 뒤, 결과를 저장해줘.

 

그러면 에이전트는 등록된 도구를 사용해서 작업을 처리합니다.

코드

import os
from pathlib import Path

from dotenv import load_dotenv
from langchain.agents import create_agent
from langchain.tools import tool
from langchain_openai import ChatOpenAI


ROOT_DIR = Path(__file__).parent
DATA_DIR = ROOT_DIR / "data"
OUTPUT_DIR = ROOT_DIR / "outputs"


@tool
def read_profile() -> str:
    """
    사용자의 기본 프로필 정보를 읽습니다.
    """
    path = DATA_DIR / "profile.md"
    return path.read_text(encoding="utf-8")


@tool
def read_work_history() -> str:
    """
    사용자의 업무 경험 정보를 읽습니다.
    """
    path = DATA_DIR / "work_history.md"
    return path.read_text(encoding="utf-8")


@tool
def read_target_role() -> str:
    """
    목표 직무 또는 채용공고 정보를 읽습니다.
    """
    path = DATA_DIR / "target_role.md"
    return path.read_text(encoding="utf-8")


@tool
def save_resume_draft(filename: str, content: str) -> str:
    """
    작성된 이력서 초안을 outputs 폴더에 저장합니다.
    """
    OUTPUT_DIR.mkdir(exist_ok=True)

    safe_filename = filename.replace("/", "_").replace("\\", "_")
    if not safe_filename.endswith(".md"):
        safe_filename = f"{safe_filename}.md"

    path = OUTPUT_DIR / safe_filename
    path.write_text(content, encoding="utf-8")

    return f"저장 완료: {path}"


SYSTEM_PROMPT = """
너는 ResumeWriterAgent다.

역할:
- 사용자의 업무 경험을 분석한다.
- 목표 직무와 연결되는 경험을 찾는다.
- 이력서와 포트폴리오 초안을 markdown 형식으로 작성한다.
- 주어진 정보에 없는 사실은 절대 지어내지 않는다.
- 부족한 정보는 '추가로 확인할 내용'에 질문으로 남긴다.

작업 순서:
1. read_profile 도구로 기본 프로필을 읽는다.
2. read_work_history 도구로 업무 경험을 읽는다.
3. read_target_role 도구로 목표 직무를 읽는다.
4. 이력서 초안을 작성한다.
5. save_resume_draft 도구로 결과를 저장한다.

출력 형식:

# 이력서 초안

## 한 줄 소개

## 핵심 역량

## 경력 요약

## 프로젝트 경험

## 포트폴리오 설명

## 추가로 확인할 내용
"""


def build_agent():
    load_dotenv()

    model_name = os.getenv("OPENAI_MODEL", "gpt-5.5")

    model = ChatOpenAI(
        model=model_name,
        temperature=0.2,
    )

    agent = create_agent(
        model=model,
        tools=[
            read_profile,
            read_work_history,
            read_target_role,
            save_resume_draft,
        ],
        system_prompt=SYSTEM_PROMPT,
        name="resume_writer_agent",
    )

    return agent


def main():
    agent = build_agent()

    result = agent.invoke(
        {
            "messages": [
                {
                    "role": "user",
                    "content": """
목표 직무에 맞는 이력서 초안을 작성해줘.

필요한 정보는 등록된 도구를 사용해서 읽고,
최종 결과는 resume_draft_with_langchain.md 파일로 저장해줘.
""".strip(),
                }
            ]
        }
    )

    print(result["messages"][-1].content)


if __name__ == "__main__":
    main()

3.2 코드 설명

1. tool 데코레이터

@tool
def read_profile() -> str:

@tool을 붙이면 일반 Python 함수를 에이전트가 사용할 수 있는 도구로 등록할 수 있습니다.

도구 함수에는 docstring을 잘 적는 것이 중요합니다.

에이전트는 함수 이름과 설명을 보고 언제 이 도구를 사용할지 판단하기 때문입니다.

2. create_agent

agent = create_agent(
    model=model,
    tools=[...],
    system_prompt=SYSTEM_PROMPT,
    name="resume_writer_agent",
)

create_agent는 모델, 도구, 시스템 프롬프트를 묶어서 에이전트를 만들어줍니다.

직접 Python으로 구현한 버전에서는 제가 코드 안에서 순서를 고정했습니다.

LangChain 버전에서는 에이전트에게 도구를 제공하고, 어떤 도구를 사용할지는 모델이 판단할 수 있게 됩니다.

3. invoke

result = agent.invoke(
    {
        "messages": [
            {
                "role": "user",
                "content": "..."
            }
        ]
    }
)

LangChain 에이전트는 messages 형태로 입력을 받습니다.

이 방식은 대화 기록을 확장하기 좋고, 나중에 메모리나 멀티 에이전트 구조로 확장하기에도 좋습니다.

3.3 직접 구현 방식과 LangChain 방식 비교

구분 Pure Python LangChain
구조 이해
Python을 이해하면 쉽다 러닝커브가 있음
도구 등록 직접 구현 @tool로 등록
실행 흐름 코드에서 고정 에이전트가 도구를 직접 사용
확장성 기능이 길어질 수록 복잡함 확장성 좋음

 

정리

이번 글에서는 AI 에이전트의 기본 개념을 정리하고, ResumeWriterAgent를 두 가지 방식으로 만들어봤습니다.

첫 번째는 LangChain 없이 Python과 OpenAI API만 사용한 방식입니다.

두 번째는 LangChain의 tool create_agent를 사용한 방식입니다.

아직은 단일 에이전트지만, 최종 목표는 제 업무 경험을 기반으로 이력서와 포트폴리오를 작성하는 AI 오케스트레이션 시스템을 만드는 것입니다.

다음 글에서는 하나의 에이전트를 여러 전문 에이전트로 나누는 멀티 에이전트 구조를 만들어보겠습니다.

반응형

'AI' 카테고리의 다른 글

[NEWS] Marrrang Dev News - 26.04.24  (0) 2026.04.24
[AI] Gemma 사용해보기  (1) 2024.03.16