OpenAI SDK - Message와 Response 구조
Message 구조
messages 배열에 들어가는 각 메시지는 role로 구분된다.
system
{"role": "system", "content": "너는 검색 전문가야"}
user
{"role": "user", "content": "파이썬 GIL이 뭔지 검색해줘"}
assistant — 두 가지 형태
# 텍스트 응답
{"role": "assistant", "content": "GIL은..."}
# tool call 요청 (content는 None일 수 있음)
{
"role": "assistant",
"content": None,
"tool_calls": [
{
"id": "call_abc123",
"type": "function",
"function": {
"name": "search_web",
"arguments": '{"query": "python GIL"}' # JSON 문자열
}
}
]
}
tool — 실행 결과
{
"role": "tool",
"tool_call_id": "call_abc123", # assistant의 tool_call id와 매칭
"content": "검색 결과: ..."
}
tool_call_id로 "이 결과가 어느 요청에 대한 것인지" 매칭한다. 모델이 여러 tool을 동시에 요청할 수 있기 때문에 필수.
Response 구조
response.choices[0].finish_reason # "stop" or "tool_calls"
response.choices[0].message # assistant message 객체
response.choices[0].message.content # 텍스트 (tool_calls면 None)
response.choices[0].message.tool_calls # tool call 요청 목록 (없으면 None)
# tool_call 하나
tool_call.id # "call_abc123"
tool_call.function.name # "search_web"
tool_call.function.arguments # '{"query": "python GIL"}' ← JSON 문자열
arguments는 문자열이다.json.loads()로 파싱해야 dict가 된다.
루프 안에서 messages 추가 순서
순서가 틀리면 모델이 맥락을 잃는다. assistant 메시지 먼저, tool 결과 나중.
# 1. assistant 응답 추가 (tool_calls 포함)
messages.append(response.choices[0].message)
# 2. 각 tool 결과 추가
for tool_call in response.choices[0].message.tool_calls:
result = execute(tool_call.function.name,
json.loads(tool_call.function.arguments))
messages.append({
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result)
})
모델 입장에서 "내가 요청했고 → 결과가 왔다"는 순서가 messages에 그대로 있어야 한다.
Multi-agent에서 다음 agent에게 뭘 넘길까
| 후보 | 내용 | 크기 |
|------|------|------|
| message.content (최종) | 모델의 최종 텍스트 답변 | 작음 |
| tool 결과들 | raw 검색 결과 등 | 클 수 있음 |
| 전체 messages 배열 | 모든 왕복 | 큼 |
대부분의 경우 최종 message.content만 넘기는 게 맞다. 모델이 tool 결과를 이미 소화해서 content에 정리해줬으니, raw 데이터까지 넘기면 다음 agent의 context만 커진다.