| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- __repr()
- Agent Persistence
- 엑셀
- Agent class
- # 암호(비밀번호) 분실 # 암호(비밀번호) 찾기 #오피스(doc
- pathlib.Path
- agent history
- AI
- 오블완
- xls
- LLM.PY
- AGENT.PY
- list comprehension
- JSON
- 인공지능
- AI MODEL
- LLM
- #다산 정약용 #유배지에서 보낸 편지 #도덕 #용기 #염 #주역 #호연지기 #효제 #근검
- AI Agent
- MAIN.PY
- 에이전트
- call model (history)
- 파이썬
- 티스토리챌린지
- 파워포인트) #집(zip)파일 #아래한글(HWP) #brute-force(무차별 대입)
- 로그 파일(LOG FILE)
- #선진국 대한민국 #선진국 #대한민국 #아이들 #청소년 #고민 #해결 #심리
- dictionary vs class
- ppt) 파일 #오피스(워드
- TOOLS.PY
- Today
- Total
아톨러브
XII. Python 프로그램이 인터넷 너머의 API 서버와 대화하는 방법 → 오류, 재시도 ...→ 결국 실제 LLM 호출 함수 call_model()로 발전시키는 과정 본문
XII. Python 프로그램이 인터넷 너머의 API 서버와 대화하는 방법 → 오류, 재시도 ...→ 결국 실제 LLM 호출 함수 call_model()로 발전시키는 과정
아톨 2026. 8. 25. 19:57
#-------- 1. HTTP 상태 코드
# 200 OK, it worked
# 400 bad request, your JSON or parameters are wrong
# 401 unauthorised, the API key is missing or invalid
# 403 forbidden, the key is valid but not allowed to do this
# 404 not found, check the URL
# 429 too many requests, slow down and retry later
# 500 server error on their side
# 503 service unavailable, retry later
#-------- 2. GET 요청 보내기
import requests
response = requests.get("https://httpbin.org/json", timeout=10)
print("status: ", response.status_code)
print("content type: ", response.headers["content-type"])
data = response.json()
print("top level keys: ", list(data))
# status: 200
# content type: application/json
# top level keys: ['slideshow']
#-------- 3. POST 요청 + JSON 보내기
import os
import requests
API_KEY = os.environ.get("MODEL_API_KEY", "test-key")
payload = {
"model":"some-model",
"max_tokens":256,
"messages":[{"role":"user", "content":"다섯 단어로 인사를 하세요."}],
}
headers = {
"Authorization":f"Bearer {API_KEY}",
"Content-Type":"application/json",
}
response = requests.post(
json=payload,
headers=headers,
timeout=30,
)
echo = response.json()
print("status: ", response.status_code)
print("sent model: ", echo["json"]["model"])
print("sent message: ", echo["json"]["messages"][0]["content"])
print("auth header seen: ", echo["headers"]["Authorization"][:11] + "...")
#-------- 4. HTTP 상태 코드 확인하기
import requests
for url in ["https://httpbin.org/status/200", "https://httpbin.org/status/404"]:
r = requests.get(url, timeout=10)
print(f"{url.split('/')[-1]}: ok={r.ok} code={r.status_code}")
try:
r.raise_for_status()
print("진행~")
except requests.HTTPError as e:
print(f"중지되었습니다: {e.response.status_code} error")
#-------- 5. 실패하면 Retry 하기
import requests
import time
RETRY_CODES = {429, 500, 502, 503, 504}
def get_with_retry(url, tries=3, timeout=10):
for attempt in range(1, tries+1):
try:
r = requests.get(url, timeout=timeout)
except requests.Timeout:
print(f"attempt {attempt}: timed out")
else:
if r.status_code not in RETRY_CODES:
return r
print(f"시도 {attempt}: {r.status_code} 수신, 재시도")
if attempt < tries:
time.sleep(0.5*(2**(attempt-1)))
return None
result = get_with_retry("https://httpbin.org/status/503")
print("최종 결과: ", result)
ok = get_with_retry("https://httpbin.org/status/200")
print("좋은 신호로 돌아옴: ", ok.status_code)
#-------- 6. Streaming 응답 받기
import requests
with requests.get("https://httpbin.org/stream/3", stream=True, timeout=30) as r:
for number, line in enumerate(r.iter_lines(), start=1):
if line:
print(f"청크 {number}: {len(line)} bytes 수신되었습니다.")
print("stream 종료되었습니다.")
#-------- 7. 모든 것을 합쳐 call_model() 만들기
import os
import time
import requests
API_URL="https://httpbin.org/post"
API_KEY=os.environ.get("MODEL_API_KEY", "test_key")
RETRY_CODES={429, 500, 502, 503, 504}
def call_model(messages, model="some-model", max_tokens=512, tries=3):
payload={"model":model, "max_tokens":max_tokens, "messages":messages}
headers={
"Authorization":f"Bearer {API_KEY}",
"Content-Type":"application/json",
}
for attempt in range(1, tries+1):
try:
r = requests.post(API_URL, json=payload, headers=headers, timeout=30)
except requests.RequestException as e:
if attempt==tries:
return {"ok":False, "text":None, "error":f"network: {e}"}
time.sleep(0.5*(2**(attempt-1)))
continue
if r.status_code in RETRY_CODES and attempt < tries:
wait=float(r.headers.get("Retry-After", 0.5*(2**(attempt-1))))
print(f"{r.status_code} received, waiting {wait}s")
time.sleep(wait)
continue
if not r.ok:
return {"ok":False, "text":None, "error":f"http {r.status_code}"}
try:
data = r.json()
print("data: ", data)
except ValueError:
return {"ok":False, "text":None, "error":"response was not JSON"}
#httpbin echoes the request back; a real API returns generated text here
text = data["json"]["messages"][-1]["content"]
print("text: ", text)
return {"ok":True, "text":text, "error":None}
return {"ok":False, "text":None, "error":"exhausted retries"}
result = call_model([{"role":"user", "content":"우리의 환불정책은 무엇인가요?"}])
print("ok: ", result["ok"])
print("text: ", result["text"])
print("error: ", result["error"])
우리가 만들 Agent 구조에서 보면:
User
↓
Agent
↓
call_model() ← 이번 코드의 최종 목적
↓
HTTP Request
↓
OpenAI API
↓
HTTP Response
↓
JSON
↓
Agent가 이해할 수 있는 데이터
순서대로 보겠습니다.
전체 코드의 큰 흐름
이번 코드는 사실 6단계의 실습입니다.
1. GET 요청 보내기
↓
2. POST 요청 + JSON 보내기
↓
3. HTTP 상태 코드 확인하기
↓
4. 실패하면 Retry 하기
↓
5. Streaming 응답 받기
↓
6. 모든 것을 합쳐 call_model() 만들기
마지막 call_model()이 우리가 실제 Agent에서 사용할 핵심입니다.
1. HTTP 상태 코드
맨 처음 주석입니다.
# 200 OK, it worked
# 400 bad request, your JSON or parameters are wrong
# 401 unauthorised, the API key is missing or invalid
# 403 forbidden, the key is valid but not allowed to do this
# 404 not found, check the URL
# 429 too many requests, slow down and retry later
# 500 server error on their side
# 503 service unavailable, retry later
이것은 서버와 통신했을 때 받는 HTTP 상태 코드입니다.
Agent를 만들 때 굉장히 중요합니다.
상태 코드 그림
Python 프로그램
│
│ HTTP 요청
▼
┌───────────────┐
│ API 서버 │
└───────┬───────┘
│
│ HTTP 응답
▼
┌────────────────┐
│ status_code │
│ │
│ 200 │ 성공
│ 400 │ 요청 오류
│ 401 │ 인증 오류
│ 429 │ 너무 많은 요청
│ 500 │ 서버 오류
│ 503 │ 서버 일시 장애
└────────────────┘
Agent에서 중요한 분류
저는 이것을 두 그룹으로 나누어 기억하는 것을 추천합니다.
그룹 1: 내가 고쳐야 하는 오류
400
401
403
404
예를 들어:
400 → JSON 구조가 틀림
401 → API Key가 틀림
403 → 권한 없음
404 → URL이 틀림
이런 것은 단순 재시도를 해도 대부분 해결되지 않습니다.
틀린 API Key
↓
재시도
↓
틀린 API Key
↓
재시도
↓
틀린 API Key
계속 실패합니다.
그룹 2: 잠시 후 다시 시도할 가치가 있는 오류
429
500
502
503
504
예:
503 Service Unavailable
서버가 잠시 바쁜 경우:
지금 실패
↓
0.5초 대기
↓
다시 요청
↓
성공 가능
이것이 뒤에서 배우는 Retry입니다.
2. 첫 번째 코드 — GET 요청
import requests
requests란?
Python에서 인터넷 서버와 통신할 때 많이 사용하는 라이브러리입니다.
Python
│
│ requests
▼
Internet
│
▼
API Server
대표적으로:
requests.get()
읽어오기
requests.post()
데이터 보내기
입니다.
요청 보내기
response = requests.get(
"https://httpbin.org/json",
timeout=10
)
작은 블록으로 보겠습니다.
requests.get()
requests.get(URL)
뜻:
이 URL에 GET 요청을 보내라.
URL
"https://httpbin.org/json"
httpbin.org는 HTTP 요청을 연습하기 좋은 테스트 서버입니다.
실제 OpenAI API 대신:
Python
↓
httpbin
↓
응답 확인
을 연습하는 것입니다.
timeout=10
timeout=10
뜻:
최대 10초만 기다려라.
만약 서버가 응답하지 않는다면:
요청
↓
1초
↓
5초
↓
10초
↓
응답 없음
↓
Timeout Exception
이 됩니다.
이것이 중요한 이유는 API 서버가 문제가 생겼을 때 Agent가 영원히 멈추는 것을 방지하기 위해서입니다.

3. 응답 객체 response
response = requests.get(...)
여기서 response는 단순 문자열이 아닙니다.
대략 이런 구조입니다.
response
│
├── status_code
│
├── headers
│
├── text
│
├── json()
│
├── ok
│
└── raise_for_status()
즉:
response.status_code
성공 여부
response.headers
응답 헤더
response.json()
JSON 데이터 변환
등을 제공합니다.
4. status_code 출력
print("status: ", response.status_code)
예:
status: 200
이것은:
서버와 통신이 성공했다.
라는 뜻입니다.
5. headers
print(
"content type: ",
response.headers["content-type"]
)
서버 응답에는 여러 정보가 들어 있습니다.
HTTP Response
│
├── Headers
│ │
│ ├── Content-Type
│ ├── Date
│ └── Server
│
└── Body
│
└── 실제 데이터
여기서:
Content-Type: application/json
이면:
서버가 JSON 데이터를 보내고 있습니다.
라는 뜻입니다.
잠재적인 문제점
현재 코드는:
response.headers["content-type"]
입니다.
만약 해당 헤더가 없으면:
KeyError
가 발생할 수 있습니다.
조금 더 안전하게 하려면:
response.headers.get("content-type")
를 사용할 수 있습니다.
하지만 학습용 코드로는 현재 방식도 괜찮습니다.
6. JSON 변환
data = response.json()
서버가 보내온 데이터가 예를 들어:
{
"slideshow": {
"title": "Sample"
}
}
라고 하면:
response.json()
은 Python Dictionary로 바꿔줍니다.
data = {
"slideshow": {
"title": "Sample"
}
}
7. top level keys
print("top level keys: ", list(data))
data가:
{
"slideshow": {
...
}
}
이면:
list(data)
는:
["slideshow"]
입니다.
즉:
data
│
└── "slideshow"
여기서 중요한 Python 문법입니다.
Dictionary를 list()로 감싸면?
list(data)
는 기본적으로 Dictionary의 Key들을 가져옵니다.
예:
person = {
"name": "Kim",
"age": 50
}
print(list(person))
결과:
['name', 'age']
첫 번째 실습의 핵심
GET 요청
↓
Response 객체 받음
↓
status_code 확인
↓
headers 확인
↓
JSON → Python Dictionary
이것이 API 통신의 가장 기본입니다.
8. 두 번째 코드 — POST로 JSON 보내기
이제 더 중요합니다.
import os
import requests
두 개를 가져옵니다.
os
↓
환경변수
requests
↓
인터넷 API 통신
9. API_KEY
API_KEY = os.environ.get(
"MODEL_API_KEY",
"test-key"
)
이것은 우리가 전에 공부한 환경변수입니다.
뜻:
환경변수 MODEL_API_KEY가 있으면
↓
그 값을 사용
없으면
↓
"test-key" 사용
예:
MODEL_API_KEY=abc123
이면:
API_KEY = "abc123"
없으면:
API_KEY = "test-key"
10. payload
payload = {
"model":"some-model",
"max_tokens":256,
"messages":[
{
"role":"user",
"content":"다섯 단어로 인사를 하세요."
}
],
}
이 부분은 매우 중요합니다.
왜냐하면 실제 OpenAI API를 호출할 때도 이런 식으로:
Python Dictionary → JSON 요청 데이터
를 만드는 과정이 필요하기 때문입니다.
구조를 그림으로 보면:
payload
│
├── model
│ └── "some-model"
│
├── max_tokens
│ └── 256
│
└── messages
│
└── List
│
└── Dictionary
│
├── role
│ └── user
│
└── content
└── "다섯 단어로..."
즉:
payload["messages"][0]["content"]
이 가능합니다.
하나씩 보면:
payload
↓
messages
↓
첫 번째 메시지 [0]
↓
content
11. headers
headers = {
"Authorization": f"Bearer{API_KEY}",
"Content-Type": "application/json",
}
HTTP 요청에는 크게 두 가지가 있습니다.
HTTP Request
│
├── Headers
│
└── Body
Authorization
"Authorization": f"Bearer {API_KEY}"
일반적으로 의미는:
이 요청은 이 API Key를 가진 사용자가 보낸 것입니다.
입니다.
주의할 점
첫 번째 코드에는:
f"Bearer{API_KEY}"
라고 되어 있습니다.
여기에는 Bearer와 API Key 사이에 공백이 없습니다.
Bearerabc123
일반적인 형식은:
Bearer abc123
즉:
f"Bearer {API_KEY}"
입니다.
아래 마지막 call_model() 코드에서는 제대로 수정되어 있습니다.
"Authorization": f"Bearer {API_KEY}"
이 부분은 실제 API에서는 매우 중요합니다.
12. POST 요청
response = requests.post(
"https://httpbin.org/post",
json=payload,
headers=headers,
timeout=30,
)
이제 전체 구조입니다.
Python
payload
│
├── model
├── max_tokens
└── messages
headers
│
├── Authorization
└── Content-Type
↓
requests.post()
↓
HTTP Server
json=payload
여기서 매우 중요합니다.
json=payload
는 requests에게:
이 Python Dictionary를 JSON으로 변환해서 보내세요.
라는 의미입니다.
즉:
payload = {
"model": "some-model"
}
↓
인터넷으로:
{
"model": "some-model"
}
형태로 전송됩니다.
13. 서버가 받은 데이터를 다시 확인
echo = response.json()
httpbin은 테스트 서버라서 재미있는 기능이 있습니다.
우리가 보낸 요청을 다시 돌려줍니다.
내가 보낸 것
↓
httpbin
↓
그대로 돌려줌
그래서:
echo["json"]["model"]
로 확인할 수 있습니다.
구조
echo
│
├── json
│ │
│ ├── model
│ │
│ └── messages
│ │
│ └── [0]
│ │
│ └── content
│
└── headers
│
└── Authorization
그래서:
echo["json"]["model"]
↓
some-model
echo["json"]["messages"][0]["content"]
↓
다섯 단어로 인사를 하세요.
14. HTTP 상태 코드 확인
다음 코드입니다.
for url in [
"https://httpbin.org/status/200",
"https://httpbin.org/status/404"
]:
우리가 배운 for문입니다.
두 개의 URL을 하나씩 꺼냅니다.
1회차
url
↓
https://httpbin.org/status/200
다음:
2회차
url
↓
https://httpbin.org/status/404
15. r.ok
print(
f"{url.split('/')[-1]}: "
f"ok={r.ok} "
f"code={r.status_code}"
)
예:
200: ok=True code=200
404: ok=False code=404
r.ok는 간단하게:
성공 범위?
를 판단하는 Boolean 값입니다.
True
False
16. raise_for_status()
try:
r.raise_for_status()
print("진행~")
except requests.HTTPError as e:
print(
f"중지되었습니다: "
f"{e.response.status_code} error"
)
이 부분은 매우 중요합니다.
200인 경우
r.raise_for_status()
↓
오류 없음
↓
"진행~"
404인 경우
r.raise_for_status()
↓
HTTPError 발생
↓
except
↓
"중지되었습니다: 404 error"
왜 이렇게 사용하는가?
API 프로그램에서는:
r = requests.get(...)
만 했다고 성공한 것이 아닙니다.
예:
인터넷 연결 성공
↓
서버까지 도착
↓
하지만 서버가
404 반환
입니다.
그래서:
r.raise_for_status()
를 사용하면:
HTTP 오류를 Python Exception으로 바꿔줍니다.
이것이 편리한 이유입니다.
HTTP 오류
↓
Exception
↓
try / except로 통일해서 처리
17. Retry 코드
이제 Agent에서 정말 중요한 부분입니다.
RETRY_CODES = {
429,
500,
502,
503,
504
}
이 Dictionary가 아니라 Set입니다.
{429, 500, 502}
이 형태는 Python Set입니다.
왜 Set을 사용하는가?
우리는 계속 확인합니다.
if r.status_code in RETRY_CODES:
즉:
이 상태 코드가 재시도 목록에 있는가?
Set은 이런 포함 여부 확인에 적합합니다.
18. get_with_retry()
def get_with_retry(
url,
tries=3,
timeout=10
):
뜻:
url
↓
요청할 주소
tries=3
↓
최대 3번 시도
timeout=10
↓
각 요청당 최대 10초 기다림
19. for attempt
for attempt in range(1, tries+1):
예:
tries = 3
이면:
attempt
1
2
3
총 3번입니다.
20. try / except / else
try:
r = requests.get(url, timeout=timeout)
except requests.Timeout:
print(f"attempt {attempt}: timed out")
else:
...
여기서 else가 중요합니다.
구조:
try
↓
성공
↓
else 실행
try
↓
Exception
↓
except 실행
즉:
else:
는:
try에서 오류가 발생하지 않았을 때 실행
됩니다.
21. 재시도 여부
if r.status_code not in RETRY_CODES:
return r
이 한 줄은 매우 중요한 판단입니다.
예를 들어:
200
은 RETRY_CODES에 없습니다.
따라서:
return r
성공 종료합니다.
그런데:
503
은:
RETRY_CODES = {
429,
500,
502,
503,
504
}
안에 있습니다.
그래서:
재시도
합니다.
22. Exponential Backoff
time.sleep(
0.5 * (2 ** (attempt - 1))
)
이 부분이 조금 어려울 수 있습니다.
하나씩 보겠습니다.
첫 번째 시도 실패
attempt = 1
0.5 × 2^(1-1)
0.5 × 1
= 0.5초
두 번째 시도 실패
attempt = 2
0.5 × 2^(2-1)
0.5 × 2
= 1초
세 번째 시도 실패
attempt = 3
0.5 × 2^(3-1)
0.5 × 4
= 2초
즉:
1번째 실패 → 0.5초
2번째 실패 → 1초
3번째 실패 → 2초
4번째 실패 → 4초
점점 기다리는 시간이 늘어납니다.
이것을 Exponential Backoff(지수 백오프)라고 합니다.
왜 그냥 계속 재시도하지 않는가?
만약 서버가:
너무 바쁩니다!
라고 했는데:
요청
요청
요청
요청
요청
요청
계속 보내면 서버는 더 힘들어집니다.
그래서:
실패
↓
잠시 기다림
↓
다시 요청
↓
더 오래 기다림
↓
다시 요청
하는 것입니다.
23. Streaming
다음 코드입니다.
with requests.get(
"https://httpbin.org/stream/3",
stream=True,
timeout=30
) as r:
Streaming은:
서버가 결과 전체를 한 번에 보내지 않고 조금씩 보내는 방식
입니다.
일반 응답
서버
│
│████████████████
│ 전체 데이터
▼
Python
Streaming
서버
│
│██
▼
Python
서버
│
│██
▼
Python
서버
│
│██
▼
Python
with
with requests.get(...) as r:
이것은:
작업이 끝나면 연결을 자동으로 정리하라.
는 의미입니다.
우리가 배운 파일과 비슷합니다.
with open("file.txt") as f:
...
파일 작업이 끝나면 자동으로 닫습니다.
HTTP도:
with requests.get(...) as r:
작업이 끝나면 연결을 정리합니다.
iter_lines()
for number, line in enumerate(
r.iter_lines(),
start=1
):
iter_lines():
응답 데이터를 한 줄씩 가져온다.
그리고:
enumerate(..., start=1)
로:
번호 + 데이터
를 동시에 받습니다.
예:
1번째 줄
2번째 줄
3번째 줄
24. 이제 핵심: call_model()
이제 앞의 모든 것을 합쳤습니다.
def call_model(
messages,
model="some-model",
max_tokens=512,
tries=3
):
이 함수는 Agent 전체 구조에서 여기입니다.
Agent.run()
↓
history
↓
call_model(messages) ★
│
│
▼
HTTP POST
│
▼
LLM API
│
▼
Response
│
▼
Python Dictionary
25. 함수 입력
messages
Agent의 History입니다.
예:
messages = [
{
"role": "system",
"content": "You are a helpful agent."
},
{
"role": "user",
"content": "7개의 가격을 계산해줘"
}
]
즉:
History
↓
call_model()
↓
API
입니다.
26. payload 만들기
payload = {
"model": model,
"max_tokens": max_tokens,
"messages": messages
}
이 구조는 매우 중요합니다.
call_model()
│
├── model
│
├── max_tokens
│
└── messages
│
└── Agent History
27. headers
headers = {
"Authorization": f"Bearer {API_KEY}",
"Content-Type": "application/json",
}
이번에는 정확합니다.
Bearer + 공백 + API_KEY
28. RequestException
except requests.RequestException as e:
이것은 매우 좋은 구조입니다.
requests에서 발생할 수 있는 여러 네트워크 오류를 비교적 넓게 잡습니다.
예:
ConnectionError
Timeout
Request 문제
등입니다.
마지막 시도인가?
if attempt == tries:
예:
tries = 3
attempt = 3
이면:
마지막 시도
입니다.
그래서:
return {
"ok": False,
"text": None,
"error": f"network: {e}"
}
결과 구조
성공하든 실패하든 비슷한 구조를 유지합니다.
result
│
├── ok
│
├── text
│
└── error
예:
{
"ok": True,
"text": "안녕하세요",
"error": None
}
실패:
{
"ok": False,
"text": None,
"error": "network error"
}
이런 구조를 유지하는 것은 Agent를 만들 때 매우 좋습니다.
왜냐하면 사용하는 쪽에서 항상:
result["ok"]
result["text"]
result["error"]
를 사용할 수 있기 때문입니다.
29. Retry-After
wait = float(
r.headers.get(
"Retry-After",
0.5 * (2 ** (attempt - 1))
)
)
서버가 이런 메시지를 줄 수 있습니다.
Retry-After: 5
뜻:
5초 후 다시 시도하세요.
그러면:
wait = 5
를 사용합니다.
없다면:
0.5 * (2 ** (attempt - 1))
을 사용합니다.
구조:
Retry-After 있음?
│
┌──┴──┐
YES NO
│ │
▼ ▼
서버가 준 Exponential
시간 사용 Backoff
30. r.ok 확인
if not r.ok:
return {
"ok": False,
"text": None,
"error": f"http {r.status_code}"
}
예:
400
이라면:
r.ok = False
따라서:
실패 결과 반환
중요한 설계 포인트
여기서:
400
401
403
404
같은 오류는 재시도하지 않고 바로 반환합니다.
왜?
401
↓
API Key 틀림
↓
재시도해도 틀린 API Key
↓
계속 실패
그래서:
Permanent Error
→ 바로 종료
반면:
503
↓
서버 잠시 문제
↓
재시도 가치 있음
입니다.
31. JSON 파싱
try:
data = r.json()
print("data: ", data)
except ValueError:
return {
"ok": False,
"text": None,
"error": "response was not JSON"
}
서버가 항상 JSON을 준다는 보장은 없습니다.
예:
<html>
Server Error
</html>
를 줄 수도 있습니다.
그러면:
r.json()
실패합니다.
그래서:
JSON 변환
│
├── 성공
│ ↓
│ data
│
└── 실패
↓
error 반환
입니다.
32. 현재 httpbin의 특별한 부분
text = data["json"]["messages"][-1]["content"]
이 부분은 실제 LLM 응답을 읽는 코드가 아닙니다.
주석에도:
# httpbin echoes the request back;
# a real API returns generated text here
라고 되어 있습니다.
httpbin은:
내가 보낸 JSON
↓
그대로 돌려줌
그래서:
data["json"]["messages"][-1]["content"]
를 하면:
내가 마지막으로 보낸 사용자 메시지
가 다시 나옵니다.
예:
우리의 환불정책은 무엇인가요?
[-1]의 의미
messages[-1]
은 리스트의 마지막 요소입니다.
예:
messages = [
{"role": "system"},
{"role": "user"},
{"role": "assistant"}
]
그러면:
messages[-1]
↓
마지막 요소
↓
{"role": "assistant"}
실제 Agent에서는 어떻게 바뀌는가?
현재:
httpbin
내 요청
↓
그대로 반환
실제 LLM API:
messages
↓
LLM
↓
AI가 생각하고 응답
↓
새로운 메시지 반환
그래서 나중에는 대략:
text = response.choices[0].message.content
또는 사용하는 API의 실제 응답 구조에 맞는 코드를 사용하게 됩니다.
전체 call_model() 실행 흐름
마지막으로 전체 흐름을 그림으로 정리해보겠습니다.
call_model(messages)
│
▼
┌───────────────────┐
│ payload 생성 │
│ model │
│ max_tokens │
│ messages │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ headers 생성 │
│ Authorization │
│ Content-Type │
└─────────┬─────────┘
│
▼
┌───────────────────┐
│ POST 요청 │
└─────────┬─────────┘
│
├──── Timeout?
│ │
│ ▼
│ Retry
│
▼
HTTP Response
│
├──── 503?
│ │
│ ▼
│ Retry
│
├──── 400?
│ │
│ ▼
│ Error 반환
│
▼
200 OK
│
▼
JSON 변환
│
├── 실패 → Error
│
▼
Text 추출
│
▼
{
"ok": True,
"text": "...",
"error": None
}
이번 코드에서 발견한 중요한 문제점 3가지
① Bearer 뒤 공백
두 번째 코드:
"Authorization": f"Bearer{API_KEY}"
보다는:
"Authorization": f"Bearer {API_KEY}"
가 맞습니다.
② Retry-After는 항상 숫자가 아닐 수도 있음
현재:
wait = float(r.headers.get(...))
서버에 따라 Retry-After가 날짜 형식으로 올 가능성도 있어서 실제 서비스에서는 조금 더 안전하게 처리할 필요가 있습니다.
하지만 현재 학습 단계에서는 충분히 좋은 코드입니다.
③ 마지막 call_model()은 아직 진짜 모델을 호출하지 않는다
API_URL = "https://httpbin.org/post"
이기 때문입니다.
즉 지금은:
Agent
↓
가짜 API
↓
요청 확인
단계입니다.
다음 단계에서는:
Agent
↓
실제 OpenAI API
↓
GPT 응답
으로 바뀌게 됩니다.
이번 코드에서 가장 중요한 핵심
이번 코드 전체를 한 문장으로 요약하면:
LLM을 호출하는 것은 결국 Python이 HTTP POST 요청으로 JSON을 보내고, 서버가 돌려주는 JSON 응답을 안전하게 받아오는 과정이다.
그리고 Agent 관점에서는:
history
↓
call_model(history)
↓
HTTP POST
↓
LLM API
↓
Response
↓
JSON parsing
↓
text
↓
Agent
입니다.
'AI, 클라우드, 문서, 자동화 > AI_AGENT' 카테고리의 다른 글
| XI. Agent의 핵심 부품을 여러 파일로 나누어 실제 프로젝트 구조 만들기 (0) | 2026.08.24 |
|---|---|
| X. Agent의 여러 요소를 하나의 class Agent로 묶어가는 과정. (0) | 2026.08.24 |
| IX. AI Agent에 “기억과 기록” 기능을 붙이는 단계 (0) | 2026.08.23 |
| VIII. Agent Loop + Tool Dispatcher 다음 단계인 예외 처리(Exception), 재시도(Retry), Logging 스터디 (0) | 2026.08.23 |
| VII. Python 함수 → Tool 함수 → Agent의 Tool Dispatcher 구조 (0) | 2026.08.23 |
