AI Agent는 더 똑똑한 챗봇이 아니다

회사 IT 헬프데스크에 이런 메시지가 들어왔다고 해 보겠습니다. 티켓은 헬프데스크에 들어온 요청 한 건을 기록해 두는 접수 번호표입니다. 같은 LLM을 쓰는 프로그램 세 개가 이 요청을 받으면 어떻게 될까요? 차이는 모델의 똑똑함이 아니라 다음에 할 일을 누가 정하느냐에서 생깁니다.

직원
노트북 와이파이가 안 잡혀요. 티켓 좀 열어 주세요.
상황

스크롤을 내리면 세 칸이 한 단계씩 진행됩니다

옆으로 밀면 세 칸을 차례로 볼 수 있습니다 →

챗봇

LLM이 답을 글로 씁니다. 그다음은 사람이 합니다.

    워크플로

    개발자가 미리 정해 둔 순서대로 갑니다.

      Agent

      LLM이 결과를 보고 다음 할 일을 고릅니다.

        점 색 = 그 단계를 하자고 정한 쪽 사람미리 짠 코드모델(LLM)

        설명을 위해 만든 예시 시나리오입니다. 실제 모델이 늘 이렇게 판단하지는 않습니다. 상황을 바꿔 보세요. 챗봇과 워크플로는 같은 길로 가고 Agent만 길을 바꿉니다.

        LLM은 글을 이어 쓰는 프로그램입니다

        세 프로그램 모두 안에 같은 LLM이 들어 있습니다. LLM(Large Language Model)은 엄청나게 많은 글을 읽고 배운 프로그램입니다. 받은 글 뒤에 올 말을 이어 씁니다. 휴대폰 키보드가 다음 단어를 추천하는 것과 비슷한데, 한 단어 대신 긴 답을 통째로 씁니다.

        LLM은 글을 토큰 단위로 읽고 씁니다. 토큰은 단어보다 조금 작은 글 조각입니다. 모델에게 주는 글은 프롬프트라고 부릅니다. 아래에서 티켓을 열어 달라고 보내 보세요.

        LLM이 "티켓을 열었습니다"라고 써도 티켓은 생기지 않습니다. 실제로 일어난 일은 글자가 나온 것뿐입니다. LLM은 회사 시스템에 직접 접속하지 못합니다. 접속했다는 문장을 쓸 수 있을 뿐입니다.

        그래서 세 프로그램의 차이는 LLM 바깥에 있습니다. LLM이 쓴 글을 받아서 무엇을 하느냐가 프로그램마다 다릅니다.

        보내는 글(프롬프트)노트북 와이파이가 안 잡혀요. 티켓 좀 열어 주세요.
        돌아오는 글
        진짜 티켓 시스템: 새로 생긴 티켓 0건
        LLM이 무슨 글을 쓰든 여기는 바뀌지 않습니다.

        돌아오는 글은 토큰 단위로 하나씩 이어 붙습니다. 토큰을 나누는 방식은 설명을 위해 단순하게 그렸습니다.

        코드에서 LLM을 한 번 부르면 오가는 것

        프로그램이 LLM을 쓸 때는 API로 부릅니다. API는 다른 프로그램에 일을 시키는 정해진 주문 양식입니다. 식당 주문서처럼 칸이 정해져 있어서 그 칸을 채워 보내면 답이 돌아옵니다.

        요청에는 대화가 메시지 목록으로 들어갑니다. 메시지마다 누가 한 말인지 적는 role이 붙습니다. system은 개발자가 모델에게 주는 지시, user는 사용자의 말, assistant는 모델이 앞서 한 말입니다.

        보내는 요청

        1
        base_url 어느 주소로 보낼지비우면 OpenAI
        2
        model 어떤 모델에게 시킬지예: gpt-5.5
        3
        messages 대화 목록 system 너는 IT 헬프데스크 상담원이다. user 와이파이가 안 잡혀요. 티켓 열어 주세요.
        →보내면
        답이 옴

        돌아오는 응답

        4
        choices[0].message 모델이 쓴 답 assistant 와이파이를 껐다 켜 보세요. 그래도 안 되면 티켓을 열어 드릴게요.
        5
        usage 쓴 토큰 수요금과 속도가 이 숫자로 정해집니다

        번호는 아래 코드의 번호 표지와 같습니다. 답 문장은 설명을 위한 예시입니다.

        Python으로 쓰면 아래와 같습니다. OpenAI가 만든 openai 패키지(3.29)를 씁니다. 이런 패키지를 SDK라고 하는데, 주문서 양식을 코드로 채우기 쉽게 만들어 둔 도구 모음입니다.

        ask.pypython
        import os
        from openai import OpenAI
        
        client = OpenAI(
            api_key=os.environ["LLM_API_KEY"],
            # 1 요청을 보낼 주소
            base_url=os.getenv("LLM_BASE_URL"),
        )
        
        response = client.chat.completions.create(
            # 2 어떤 모델에게 시킬지
            model=os.getenv("LLM_MODEL", "gpt-5.5"),
            # 3 대화 목록
            messages=[
                {"role": "system", "content": "너는 IT 헬프데스크 상담원이다."},
                {"role": "user", "content": "와이파이가 안 잡혀요. 티켓 열어 주세요."},
            ],
        )
        
        # 4 모델이 쓴 답
        print(response.choices[0].message.content)
        # 5 이번 호출에 쓴 토큰 수
        print(response.usage.total_tokens)
        터미널
        $ python ask.py 와이파이를 껐다 켜 보세요. 그래도 안 되면 노트북 이름과 증상을 알려 주시면 티켓을 열어 드릴게요. 57

        출력은 예시 답입니다. 같은 코드라도 모델과 때에 따라 답 문장과 토큰 수는 달라집니다.

        1. 요청을 보낼 주소입니다. 비워 두면 OpenAI로 갑니다. 같은 주문서 양식을 받는 다른 회사 API나 내 컴퓨터에 띄운 모델 서버 주소를 넣으면, 코드를 고치지 않고 모델을 바꿀 수 있습니다.
        2. 어떤 모델에게 시킬지입니다. 코드 밖 운영체제에 저장해 두는 설정값인 환경 변수로 빼 두면, 코드를 고치지 않고 실행할 때 바꿀 수 있습니다. 모델마다 받는 옵션이 조금씩 다르니, 바꾼 뒤에는 한 번 돌려 봐야 합니다.
        3. 대화 목록입니다. 이 목록이 모델이 이번에 볼 수 있는 전부입니다. system 메시지를 빼면 모델은 자기가 헬프데스크 상담원인지 모른 채 답합니다.
        4. 모델이 쓴 답입니다. choices가 목록인 이유는 한 번에 답을 여러 개 받을 수도 있어서입니다. 보통은 첫 번째 하나만 씁니다. Python 목록은 0번부터 세서 [0]이 첫 번째입니다.
        5. 이번 호출에 쓴 토큰 수입니다. 보낸 글과 받은 글을 더한 값입니다. 요금은 토큰 수로 매겨집니다.
        temperature는 왜 안 넣었나요?

        temperature는 답을 얼마나 들쭉날쭉하게 뽑을지 정하는 옵션입니다. 그런데 GPT-5 계열처럼 답하기 전에 먼저 생각하는 추론 모델은 이 옵션을 받지 않는다는 보고가 많습니다. 넣으면 오류가 납니다. 모델을 바꿔 가며 쓸 코드라 기본 예제에서는 뺐습니다.

        OpenAI에는 Responses API도 있지 않나요?

        있습니다. OpenAI는 새 프로젝트에 Responses API를 권하면서도 위에서 쓴 Chat Completions도 계속 지원합니다. 이 글은 메시지 목록을 그대로 주고받는 Chat Completions로 설명합니다. 다른 회사 모델과 로컬 모델 서버도 대부분 이 양식을 받기 때문입니다.

        LLM API는 방금 나눈 대화를 기억하지 못합니다

        API를 한 번 부르고 나면 모델 쪽에는 아무것도 남지 않습니다. 이 API는 요청 하나를 받아 답 하나를 돌려주면 끝나는 방식이라, 앞 요청의 내용을 다음 요청에 이어 주지 않습니다. 이렇게 앞선 일을 기억하지 않는 성질을 stateless라고 합니다. state는 프로그램이 들고 있는 현재 상황, 곧 상태입니다.

        그래서 대화를 이어 가려면 지금까지의 대화 전체를 매번 다시 보내야 합니다. 단톡방에 늦게 들어온 친구에게 앞의 대화를 캡처해 보내 주는 것과 비슷합니다. 다만 친구와 달리 모델은 매번 처음 보는 사람이라서 캡처를 빼먹으면 정말로 아무것도 모릅니다.

        1번째 호출

        묶음 하나가 API 호출 한 번이며 위쪽 칸들은 실어 보낸 메시지, 굵은 테두리 칸은 이번에 새로 더한 메시지입니다. 맨 아래 "답" 말풍선은 돌아온 답이라 보낸 메시지 수에 세지 않습니다. 막대는 보낸 토큰 수이며 숫자는 설명을 위한 예시 값입니다. 스크롤을 내리면 호출이 하나씩 늘어납니다.

        대화 전체를 보내면 묶음이 매번 길어지고 토큰도 늘어납니다. 대화가 길어질수록 요금이 오르고 답도 느려집니다. 모델이 한 번에 받을 수 있는 양에도 한도가 있는데, 이 한도를 context window라고 합니다. 한도를 넘으면 요청이 거절되므로, 오래된 대화를 지우거나 줄여서 보내야 합니다.

        이번 질문만 보내면 모델은 앞에서 와이파이 이야기를 했다는 것도, 재부팅을 해 봤다는 것도 모릅니다. 대화를 기억하는 쪽은 모델이 아니라 메시지 목록을 들고 있는 내 코드입니다. 코드로는 목록에 한 줄씩 더해 가며 보내면 됩니다.

        chat.pypython
        import os
        from openai import OpenAI
        
        client = OpenAI(
            api_key=os.environ["LLM_API_KEY"],
            base_url=os.getenv("LLM_BASE_URL"),
        )
        MODEL = os.getenv("LLM_MODEL", "gpt-5.5")
        SYSTEM = "너는 IT 헬프데스크 상담원이다."
        
        # 1 대화 기록, 곧 프로그램의 상태
        messages = [{"role": "system", "content": SYSTEM}]
        
        while True:
            question = input("나: ")
            if question in ("", "끝"):
                break
            # 2 사용자의 말을 목록에 더한다
            messages.append({"role": "user", "content": question})
            # 3 목록 전체를 보낸다
            response = client.chat.completions.create(model=MODEL, messages=messages)
            answer = response.choices[0].message.content
            # 4 모델의 답도 목록에 더한다
            messages.append({"role": "assistant", "content": answer})
            print("상담원:", answer)
            print(f"[보낸 메시지 {len(messages) - 1}개]")
        터미널
        $ python chat.py 나: 와이파이가 안 잡혀요 상담원: 재부팅은 해 보셨나요? [보낸 메시지 2개] 나: 네, 해 봤는데도 그래요 상담원: 그럼 무선 드라이버를 다시 설치해 보세요. [보낸 메시지 4개] 나: 끝

        출력은 예시 대화입니다. 두 번째 질문을 보낼 때 실린 메시지가 4개로 늘어난 것만 눈여겨보면 됩니다.

        1. 대화 기록을 담을 목록입니다. system 지시 하나로 시작합니다. 이 목록이 위 그림의 묶음이자 프로그램이 들고 있는 상태입니다.
        2. 사용자의 말을 목록 끝에 더합니다. 이 줄을 빼고 이번 질문만 보내면, 위 그림에서 "이번 질문만 보내기"를 고른 것과 같습니다.
        3. 목록 전체를 보냅니다. 호출할 때마다 처음부터 지금까지가 다 실립니다.
        4. 모델의 답도 목록에 더합니다. 이걸 빼면 다음 질문을 보낼 때 모델은 자기가 앞에서 무슨 답을 했는지 모릅니다.

        두 번 물어보면 첫 호출에는 메시지 2개가, 두 번째 호출에는 4개가 실려 갑니다. 여기까지가 가장 단순한 챗봇입니다. 히어로의 첫 번째 칸과 같습니다. 모델은 답을 글로 쓰고 그 글로 무엇을 할지는 사람이 정합니다.

        다음 단계를 누가 정하느냐로 세 가지를 나눕니다

        챗봇에서 다음 단계를 정하는 쪽은 사람입니다. 그 결정을 코드나 모델에게 넘기면 워크플로와 Agent가 됩니다. 라면 끓이기로 비교하면 아래 그림과 같습니다.

        챗봇

        레시피를 알려 주는 친구 끓이는 건 내가

        워크플로

        순서가 정해진 조리기 면이 없어도 4분 돈다

        Agent

        냉장고부터 열어 보는 요리사 보고 나서 다음을 고른다
        사람이 정한다미리 짠 코드가 정한다모델이 정한다

        색은 히어로와 같은 뜻입니다. 파랑은 사람, 초록은 미리 짠 코드, 주황은 모델이 다음 단계를 정한다는 표시입니다.

        Anthropic은 2024년 12월 글 「Building effective agents」에서 둘을 이렇게 나눴습니다.

        워크플로는 LLM과 도구가 개발자가 미리 짜 둔 코드 순서대로 움직이는 시스템입니다. Agent는 일을 어떻게 진행할지, 어떤 도구를 쓸지를 LLM이 스스로 정하는 시스템입니다.Anthropic, Building effective agents (2024-12-19)를 풀어 옮김

        여기서 도구(tool)는 모델이 쓸 수 있게 열어 준 함수, 곧 이름을 붙여 둔 코드 묶음입니다. 티켓 조회, 티켓 생성 같은 것들입니다. 워크플로도 LLM과 도구를 쓰지만 어떤 순서로 쓸지는 개발자가 코드에 이미 적어 두었습니다. 히어로의 워크플로가 같은 티켓을 또 만든 것도 그 순서에 "먼저 찾아보기"가 없어서입니다.

        요리사 비유가 맞지 않는 부분도 있습니다. Agent는 개발자가 열어 준 도구만 쓸 수 있습니다. 또 틀린 판단을 알아채지 못한 채 그대로 밀고 나갈 수도 있습니다.

        Agent는 도구를 부르는 반복 하나로 움직입니다

        Agent를 이루는 부품은 네 가지입니다. 판단하는 모델, 개발자가 주는 지시(system 메시지), 모델이 부를 수 있는 도구, 지금까지 일어난 일을 담은 상태(메시지 목록)입니다. 이 넷을 엮는 것이 루프입니다. 같은 순서를 되풀이하는 반복문을 말합니다.

        개발자 Simon Willison은 2025년 9월 이것을 한 문장으로 정리했습니다. "LLM Agent는 목표를 이루려고 루프 안에서 도구를 실행한다." 아래로 스크롤하면 히어로의 Agent가 한 일이 한 단계씩 진행됩니다.

        1. 시작: 직원의 메시지가 목록에 들어왔습니다. 모델을 부릅니다.
        2. 모델이 답 대신 도구 요청서를 썼습니다. 아직 아무것도 실행되지 않았습니다.
        3. 내 코드가 요청서를 읽고 티켓 시스템을 실제로 조회해, 결과를 목록에 더했습니다.
        4. 목록 전체를 들고 모델을 다시 부릅니다. 한 바퀴를 돌았습니다.
        5. 모델이 결과를 보고 새 티켓 대신 기존 티켓에 덧붙이기로 했습니다.
        6. 코드가 메모를 실제로 추가하고 결과를 목록에 더했습니다.
        7. 다시 모델을 부릅니다. 두 바퀴째입니다.
        8. 이번에는 요청서 대신 직원에게 줄 답을 썼습니다. 루프가 끝납니다.
        모델 다음 할 일을 씀 도구 요청서 "티켓 조회해 줘" 글일 뿐, 실행 전 내 코드 도구를 실제로 실행 결과를 목록에 더함 바퀴마다 지시·도구 목록· 메시지 목록을 함께 보냄 요청서를 받으면 코드가 실행 → 답을 쓰면 끝

        상태: 메시지 목록

          왼쪽 아래 칸을 눈여겨보세요. 모델이 쓰는 것은 "티켓을 조회해 달라"는 요청서까지입니다. 이 요청서를 읽고 진짜 티켓 시스템에 접속하는 건 내 코드입니다. 이렇게 모델이 도구 사용을 요청하게 하는 기능을 Tool Calling이라고 합니다.

          모델이 어떤 도구를 쓸 수 있는지 아는 것도 코드 덕분입니다. 코드가 바퀴마다 도구 이름과 설명을 적은 목록을 메시지와 함께 보내면 모델은 그 목록 안에서 고릅니다.

          루프는 모델이 요청서 대신 사람에게 줄 답을 쓰면 끝납니다. 끝나지 않을 때를 대비해 최대 반복 횟수도 정해 둡니다. 다음 글에서는 Tool Calling 요청서가 실제로 어떻게 생겼고 코드가 그걸 어떻게 실행하는지 알아보겠습니다.

          OpenAI와 LangChain은 Agent를 어떻게 정의하나요?

          OpenAI의 「A practical guide to building agents」(2025)는 Agent를 "사용자를 대신해 일을 스스로 해내는 시스템"이라고 정의합니다. 구성요소로는 모델·도구·지시 세 가지를 꼽습니다. 상태는 따로 꼽지 않았지만 루프가 끝나는 조건(도구 호출, 정해진 출력, 오류, 최대 반복 횟수)을 같이 설명합니다. LangChain은 "LLM이 프로그램의 흐름을 정하는 시스템"이라고 정의합니다. 표현은 달라도 다음 할 일을 모델이 고른다는 점은 같습니다.

          Agent가 늘 더 나은 선택은 아닙니다

          Agent는 상황에 맞춰 길을 바꾸는 대신 대가를 치릅니다. 루프를 한 바퀴 돌 때마다 모델을 다시 부르니 시간과 토큰이 더 듭니다. 모델이 길을 고르니 같은 요청에도 매번 다르게 움직일 수 있습니다. 앞에서 한 잘못된 판단이 뒤로 이어지기도 합니다.

          그래서 Anthropic도 같은 글에서 가장 단순한 방법부터 찾으라고 권합니다. 답만 쓰면 되는 일이면 챗봇으로, 순서가 늘 같은 일이면 워크플로로 충분합니다. 상황마다 다음 할 일이 달라지고 그 판단을 모델에게 맡길 만할 때 Agent를 씁니다.

          앞으로 만들 헬프데스크 Agent

          이 시리즈에서는 히어로의 헬프데스크 Agent를 조금씩 실제로 만듭니다. 오늘 만든 것은 그림에서 주황으로 칠한 부분, 모델을 부르고 대화 기록을 들고 있는 데까지입니다.

          직원 헬프데스크 Agent 모델 호출 지시(system) + 메시지 상태: 메시지 목록 도구 요청 → 실행 → 결과 반복 FAQ·매뉴얼 검색 티켓 조회·생성 장비 상태 조회 사내 문서 티켓 시스템 도구 중 위험한 작업은 사람이 승인 직원 헬프데스크 Agent 모델 호출 지시(system) + 메시지 상태: 메시지 목록 도구 요청 → 실행 → 결과 반복 FAQ·매뉴얼 검색 티켓 조회·생성 장비 상태 조회 사내 문서 · 티켓 시스템 도구 중 위험한 작업은 사람이 승인

          주황 칸은 이 글에서 만든 부분, 점선 칸은 앞으로 만들 부분입니다. 도구와 데이터는 모두 연습용으로 새로 만듭니다.

          정리하면 챗봇, 워크플로, Agent는 같은 LLM을 씁니다. 다른 점은 LLM이 쓴 글을 받은 뒤 다음 단계를 누가 정하느냐입니다. 사람이 정하면 챗봇, 미리 짠 코드가 정하면 워크플로, 모델이 결과를 보고 정하면 Agent입니다.

          참고 자료