LLM 엔지니어링9장 · 도구를 쓰는 LLM 과 Agentic Workflow5 / 21← 이전목차다음 →MetaContents

9장. 도구를 쓰는 LLM 과 Agentic Workflow

도구 호출의 흐름

식당에서 손님이 주문하면 직원이 주문서를 주방에 넘기고, 주방이 요리를 내주면 직원이 손님에게 가져다줍니다. 직원은 요리를 하지 않습니다. 도구 호출에서 LLM 은 주문서를 쓰는 직원이고, 요리는 우리 코드가 합니다.

질문, 도구 호출 요청, 코드가 실행, 결과 돌려주기, 최종 답의 다섯 상자 흐름도

1번 사용자의 질문과 도구 목록을 모델에 보냅니다. 2번 모델이 답 대신 tool_calls(도구 이름과 인자)를 돌려줍니다. 3번 우리 코드가 그 함수를 실제로 실행합니다. 4번 결과를 role: "tool" 메시지로 덧붙여 다시 보냅니다. 5번 모델이 결과를 보고 최종 답을 씁니다. 여기서 주문서가 tool_calls, 요리가 함수의 실행 결과입니다.

메시지가 쌓이는 순서

순서role내용
1user1234 곱하기 5678 은 얼마예요?
2assistanttool_calls: calculate(expression="1234*5678")
3tool7006652 (tool_call_id 로 2번과 짝짓는다)
4assistant1234 곱하기 5678 은 7,006,652 입니다.

tool 메시지 만들기

Python
import json

# 모델이 돌려준 도구 호출을 흉내 낸 값(실습 2에서 진짜로 받는다)
call = {"id": "call_1", "name": "calculate", "arguments": '{"expression": "1234*5678"}'}
args = json.loads(call["arguments"])                 # 인자는 JSON 글자로 온다
result = 1234 * 5678                                 # 우리 코드가 실행한 결과
tool_message = {"role": "tool", "tool_call_id": call["id"], "content": json.dumps({"결과": result}, ensure_ascii=False)}
print(args)
print(tool_message)

인자는 {'expression': '1234*5678'} 로 풀리고, tool 메시지의 content 는 {"결과": 7006652} 입니다. tool_call_id 가 있어야 모델이 어느 호출의 결과인지 압니다.

코드에 나오는 함수

코드쉬운 뜻
import jsonJSON 글자를 다루는 표준 라이브러리를 불러온다
json.loads(글자)load string(글자에서 읽기). JSON 글자를 파이썬 사전으로 바꾼다
json.dumps(사전, ensure_ascii=False)dump string(글자로 쏟아 내기). 사전을 JSON 글자로 바꾼다. ensure_ascii=False 는 한글을 결 같은 기호로 바꾸지 말고 그대로 두라는 뜻이다
"role": "tool"이 메시지가 도구 쪽에서 온 결과라는 표시다
"tool_call_id": call["id"]어느 도구 호출의 결과인지 짝짓는 번호다. 모델이 준 id 를 그대로 돌려준다
"content": ...모델이 읽을 결과 글이다. 글자여야 하므로 json.dumps 로 바꿔 넣는다

이 코드가 뜻하는 것

식당의 주문서와 완성 요리 이야기로 읽습니다. 직원이 쓴 주문서에는 주문 번호와 메뉴가 적혀 있고, 주방은 요리를 내며 같은 주문 번호표를 꽂아 줍니다. 여기서 주문서가 call, 주문 번호가 id, 번호표를 꽂은 요리가 tool_message 입니다.

줄하는 일쉽게 말하면
call = {"id": "call_1", ...}모델이 돌려준 도구 호출(흉내)직원이 넘긴 주문서
args = json.loads(call["arguments"])인자 글자를 사전으로 푼다주문서의 손글씨를 읽는다
result = 1234 * 5678우리 코드가 계산한다주방이 요리한다
tool_message = {"role": "tool", "tool_call_id": ..., "content": ...}결과를 tool 메시지로 싼다요리에 주문 번호표를 꽂는다
print(args), print(tool_message)둘을 찍어 본다주문서와 완성 요리를 확인한다

출력의 'tool_call_id': 'call_1' 은 이 결과가 call_1 주문의 것이라는 표시입니다. 한 번에 도구 호출이 여러 개 오면 이 번호로 결과와 호출을 짝짓습니다.

인자 글자가 깨졌을 때

모델이 만든 인자 글자는 가끔 꼴이 깨져 옵니다. 설명을 위해 지어낸 글자 둘로 json.loads 가 어떻게 하는지 봅니다.

Python
import json

# 모델이 보냈다고 치는 인자 글자 둘(설명용으로 지어낸 값). 둘째는 닫는 괄호가 빠졌다
for raw in ['{"expression": "2+3"}', '{"expression": "2+3"']:
    try:
        print("풀림:", json.loads(raw))
    except json.JSONDecodeError as e:
        print("못 풂:", e.msg)
출력
풀림: {'expression': '2+3'}
못 풂: Expecting ',' delimiter
결과읽는 법
풀림: {'expression': '2+3'}꼴이 바른 글자는 사전으로 풀린다
못 풂: Expecting ',' delimiter닫는 } 가 없어 "쉼표 구분자(delimiter)가 와야 한다"는 오류가 났다

json.JSONDecodeError 는 JSON 을 풀다(decode) 난 오류이고, e.msg 는 그 오류의 설명 글(message)입니다. try 로 감싸 두면 프로그램이 멈추지 않고, 이 오류 글을 tool 메시지로 돌려줘 모델이 다시 쓰게 할 수 있습니다.

주의할 점

  • 인자는 글자(JSON)로 옵니다. json.loads 가 실패하거나 필요한 키가 빠질 수 있으니 실행 전에 확인합니다.
  • 모델이 요청한 도구를 실행할지는 우리가 정합니다. 목록에 없는 이름이나 위험한 인자는 실행하지 않고 오류를 돌려줍니다.
← 이전 절9장 · 도구란다음 절 →9장 · 도구 설명 쓰기