AI Agent 학습: 메모리와 Function Calling 구현


AI Agent란 무엇인가?

기본 개념

AI Agent는 단순히 LLM(Large Language Model)이 아닙니다. 정확한 정의는 다음과 같습니다:

1
AI Agent = LLM + 도구(함수) + 실행 루프

중요한 사실: LLM은 텍스트를 예측하는 엔진일 뿐이며, 실제로 코드를 실행하거나 외부 시스템과 상호작용할 수 없습니다.

그렇다면 ChatGPT가 계산을 하거나 웹 검색을 하는 것처럼 보이는 이유는? 바로 Function Calling 덕분입니다.

Agent의 작동 원리

  1. 개발자가 사용 가능한 함수 목록을 정의
  2. LLM이 사용자 요청을 분석하고 적절한 함수 선택
  3. 개발자가 선택된 함수를 실제로 실행
  4. 실행 결과를 LLM에게 다시 전달
  5. LLM이 결과를 자연어로 변환하여 최종 응답 생성

이 과정이 반복되는 루프를 Agent Loop라고 합니다.

📚 참고 자료:


메모리 시스템: AI에게 기억력 부여하기

왜 메모리가 필요한가?

OpenAI의 공식 문서에 따르면:

“The models have no memory of previous requests. Hence all relevant information must be supplied via the conversation.”

즉, GPT 모델은 이전 대화를 전혀 기억하지 못합니다. 우리가 해야 할 일은 매 요청마다 전체 대화 이력을 함께 보내주는 것입니다.

구현 방법

가장 간단한 메모리 시스템은 messages 배열을 사용하는 것입니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
messages = []

# 사용자 메시지 추가
messages.append({"role": "user", "content": "My name is Chaehyeon"})

# AI 응답 추가
messages.append({"role": "assistant", "content": "Nice to meet you, Chaehyeon!"})

# 다음 요청 시 전체 messages 배열을 API에 전달
response = client.chat.completions.create(
model="gpt-5-nano",
messages=messages # 전체 대화 이력
)

메시지 역할(Role) 시스템

OpenAI API는 3가지 메시지 역할을 구분합니다:

  • user: 사용자의 입력
  • assistant: AI의 응답
  • tool: 함수 실행 결과 (Function Calling 사용 시)

실제 작동 예시

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1번째 대화
User: "My name is Chaehyeon"
AI: "Nice to meet you, Chaehyeon!"

# messages = [
# {"role": "user", "content": "My name is Chaehyeon"},
# {"role": "assistant", "content": "Nice to meet you, Chaehyeon!"}
# ]

# 2번째 대화 - AI가 이전 대화를 기억!
User: "What is my name?"
AI: "Your name is Chaehyeon"

# messages에 새 대화가 누적됨

메모리 관리 고려사항

문제점: 대화가 길어지면 토큰 비용이 급증합니다.

예를 들어, 10번의 대화 후:

  • 1번째 요청: 100 토큰
  • 10번째 요청: 1,000 토큰 (누적된 모든 대화 포함)

해결 방법:

  1. 메시지 트리밍: 오래된 메시지 삭제
  2. 요약 생성: 긴 대화를 요약하여 압축
  3. 벡터 DB 활용: 중요한 정보만 임베딩으로 저장

📚 참고 자료:


Function Calling: AI의 손과 발 만들기

Function Calling이란?

Function Calling은 LLM이 외부 함수를 호출할 수 있게 해주는 OpenAI의 기능입니다. 2023년 6월에 처음 도입되었으며, GPT-5 ~ GPT-3.5-turbo에서 사용 가능합니다.

작동 원리

LLM은 실제로 함수를 실행하지 않습니다. 대신:

  1. 함수 설명을 읽고
  2. **”이 함수를 이런 인자로 실행하면 좋겠어요”**라고 제안
  3. 개발자가 실제 실행
  4. 결과를 받아서 자연어로 변환

Tool 정의 방법

OpenAI API에서는 함수를 “Tool”이라고 부릅니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "A function to get the weather of a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The name of the city"
}
},
"required": ["city"]
}
}
}
]

핵심 요소:

  • name: 함수 이름
  • description: 매우 중요! LLM이 이 설명을 읽고 함수 선택
  • parameters: JSON Schema 형식으로 파라미터 정의
  • required: 필수 파라미터 지정

Function Calling 실행 흐름

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
1. AI가 tool을 호출하고 싶어함

message.tool_calls 존재 확인

2. 어떤 함수와 인자가 필요한지 파악

tool_call.function.name = "get_weather"
tool_call.function.arguments = '{"city":"Seoul"}'

3. 인자를 문자열 → 딕셔너리로 변환

arguments = json.loads('{"city":"Seoul"}')
# → {"city": "Seoul"}

4. 실제 함수 가져와서 실행

function = FUNCTION_MAP["get_weather"]
result = function(**arguments)
# → "15 degrees Celsius"

5. 결과를 AI에게 전달

messages.append({
"role": "tool",
"tool_call_id": "call_xxx",
"name": "get_weather",
"content": "15 degrees Celsius"
})

6. AI가 자연어로 변환

"현재 서울 온도는 15°C입니다"

중요한 디테일: tool_calls 메시지 구조

AI가 함수를 호출하려고 할 때, 응답 메시지에는 content가 비어있고 tool_calls가 포함됩니다:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"role": "assistant",
"content": "", # 비어있음!
"tool_calls": [
{
"id": "call_m4kzxZOWvelD5Mc5iVUifI8C",
"type": "function",
"function": {
"name": "get_weather",
"arguments": '{"city":"Seoul"}'
}
}
]
}

주의: message.contentNone일 수 있으므로 message.content or ""로 처리해야 합니다.

📚 참고 자료:


실전 구현: 날씨 조회 Agent

전체 코드 구조

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import openai
import json
from openai.types.chat import ChatCompletionMessage

# 1. OpenAI 클라이언트 초기화
client = openai.OpenAI()
messages = []

# 2. 실제 함수 구현
def get_weather(city):
"""실제 날씨 API를 호출하는 함수 (여기서는 Mock)"""
return '15 degrees Celsius.'

# 3. 함수 매핑 테이블
FUNCTION_MAP = {
"get_weather": get_weather
}

# 4. Tool 정의
TOOLS = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "A function to get the weather of a city",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The name of the city"
}
},
"required": ["city"]
}
}
}
]

# 5. AI 응답 처리 함수
def process_ai_response(message: ChatCompletionMessage):
"""AI 응답을 처리하고 필요시 함수를 실행"""

# Tool 호출이 있는 경우
if message.tool_calls:
# AI의 tool_calls를 messages에 추가
messages.append({
"role": "assistant",
"content": message.content or "",
"tool_calls": [
{
"id": tool_call.id,
"type": "function",
"function": {
"name": tool_call.function.name,
"arguments": tool_call.function.arguments
}
} for tool_call in message.tool_calls
]
})

# 각 tool_call 실행
for tool_call in message.tool_calls:
function_name = tool_call.function.name
arguments = tool_call.function.arguments

print(f"Calling function: {function_name} with arguments: {arguments}")

# JSON 문자열 → 딕셔너리 변환
try:
arguments = json.loads(arguments)
except json.JSONDecodeError:
arguments = {}

# 함수 실행
function_to_run = FUNCTION_MAP.get(function_name)
result = function_to_run(**arguments)

print(f"Ran function: {function_name} with result: {result}")

# 함수 실행 결과를 messages에 추가
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"name": function_name,
"content": result
})

# 재귀 호출: AI가 함수 결과를 읽고 최종 응답 생성
call_ai()

# 일반 응답인 경우
else:
messages.append({"role": "assistant", "content": message.content})
print(f"AI: {message.content}")

# 6. AI 호출 함수
def call_ai():
"""OpenAI API를 호출하고 응답을 처리"""
response = client.chat.completions.create(
model="gpt-4",
messages=messages,
tools=TOOLS
)
process_ai_response(response.choices[0].message)

# 7. 대화 루프
while True:
message = input("Send a message to the LLM...")
if message in ['quit', 'q']:
break

messages.append({"role": "user", "content": message})
print(f"User: {message}")
call_ai()

실행 결과 분석

1
2
3
4
5
6
7
8
9
10
User: My name is Chaehyeon
AI: Nice to meet you, Chaehyeon!

User: What is my name?
AI: Your name is Chaehyeon

User: What is the weather in Seoul?
Calling function: get_weather with arguments: {"city":"Seoul"}
Ran function: get_weather with result: 15 degrees Celsius.
AI: Chaehyeon, the current temperature in Seoul is 15°C

messages 배열의 상태:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
[
{"role": "user", "content": "My name is Chaehyeon"},
{"role": "assistant", "content": "Nice to meet you, Chaehyeon!"},
{"role": "user", "content": "What is my name?"},
{"role": "assistant", "content": "Your name is Chaehyeon"},
{"role": "user", "content": "What is the weather in Seoul?"},
{
"role": "assistant",
"content": "",
"tool_calls": [{
"id": "call_m4kzxZOWvelD5Mc5iVUifI8C",
"type": "function",
"function": {
"name": "get_weather",
"arguments": '{"city":"Seoul"}'
}
}]
},
{
"role": "tool",
"tool_call_id": "call_m4kzxZOWvelD5Mc5iVUifI8C",
"name": "get_weather",
"content": "15 degrees Celsius."
},
{
"role": "assistant",
"content": "Chaehyeon, the current temperature in Seoul is 15°C"
}
]

핵심 포인트

  1. 재귀 호출: process_ai_response() 함수 내에서 call_ai()를 다시 호출합니다. 이는 AI가 함수 실행 결과를 읽고 최종 응답을 생성하기 위함입니다.

  2. tool_call_id: 각 tool call에는 고유 ID가 있으며, 함수 실행 결과를 반환할 때 이 ID를 반드시 포함해야 합니다.

  3. JSON 파싱: AI가 반환하는 arguments는 JSON 문자열이므로 json.loads()로 파싱이 필요합니다.


핵심 개념 정리

AI Agent의 3요소

요소 역할 구현 방법
LLM 자연어 이해 및 생성 OpenAI API, GPT-5-NANO
도구(Functions) 실제 작업 수행 Python 함수 + TOOLS 정의
실행 루프 Agent 동작 제어 while True + 재귀 호출

메모리 시스템 핵심

  • messages 배열로 대화 이력 관리
  • role 시스템: user, assistant, tool
  • 컨텍스트 누적: 매 요청마다 전체 이력 전달
  • 비용 관리: 토큰 수 = 전체 대화 길이

Function Calling 핵심

  • Tool 정의: name, description, parameters
  • 실행 흐름: 감지 → 파싱 → 실행 → 반환 → 재호출
  • 메시지 구조: tool_callstool role
  • 재귀 패턴: 함수 실행 후 AI 재호출

Best Practices

1. Tool Description 작성 팁

1
2
3
4
5
# ❌ 나쁜 예
"description": "get weather"

# ✅ 좋은 예
"description": "Get current weather information for a specified city. Returns temperature in Celsius and weather conditions."

2. 에러 처리

1
2
3
4
try:
arguments = json.loads(arguments)
except json.JSONDecodeError:
arguments = {} # 기본값 사용

마치며

AI Agent 개발의 핵심은 단순함에 있습니다.

Anthropic의 가이드에 따르면:

“Find the simplest solution possible, and only increase complexity when needed. This might mean not building agentic systems at all.”

간단한 Function Calling부터 시작하여, 필요에 따라 복잡도를 높여가세요. 모든 복잡한 Agent도 결국 다음 3가지 요소의 조합입니다:

  1. 메모리: messages 배열
  2. 함수: Python 함수 + Tool 정의
  3. 루프: while True + 재귀 호출

관련 프로젝트: my-first-agent

AI Agent 학습: 메모리와 Function Calling 구현

https://devch.co.kr/categories/AI/ai-agent-function-calling/

Author

Chaehyeon Lee

Posted on

2025-10-26

Updated on

2025-10-26

Licensed under

댓글