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

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

실습 2 — 도구 결과를 돌려주고 답 받기

실습 1의 도구 호출을 실제로 실행하고, 결과를 tool 메시지로 돌려줘 최종 답을 받습니다. 도구 없이 물었을 때의 답과 견줍니다.

1단계. 실습 1과 같은 준비

Python
import ast, json, operator
from openai import OpenAI

client = OpenAI(base_url="http://localhost:11434/v1", api_key="ollama")
MODEL = "qwen2.5:3b"
OPS = {ast.Add: operator.add, ast.Sub: operator.sub, ast.Mult: operator.mul, ast.Div: operator.truediv}
def safe_calc(expression):
    def walk(node):
        if isinstance(node, ast.Expression):
            return walk(node.body)
        if isinstance(node, ast.Constant) and isinstance(node.value, (int, float)):
            return node.value
        if isinstance(node, ast.BinOp) and type(node.op) in OPS:
            return OPS[type(node.op)](walk(node.left), walk(node.right))
        if isinstance(node, ast.UnaryOp) and isinstance(node.op, ast.USub):
            return -walk(node.operand)
        raise ValueError("숫자와 + - * / 괄호만 쓸 수 있습니다")
    return walk(ast.parse(expression, mode="eval"))
TOOLS = [{"type": "function", "function": {
    "name": "calculate", "description": "사칙연산 식을 계산한다. 숫자 계산이 필요하면 반드시 이 도구를 쓴다.",
    "parameters": {"type": "object", "properties": {"expression": {"type": "string", "description": "계산할 식. 예: 1234*5678"}},
                   "required": ["expression"]}}}]
question = "한 상자에 48개씩 든 사과 1,375상자가 있어요. 사과는 모두 몇 개예요?"
print(question, "· 정답:", 48 * 1375)
출력
한 상자에 48개씩 든 사과 1,375상자가 있어요. 사과는 모두 몇 개예요? · 정답: 66000

2단계. 도구 호출 받고 실행하기

Python
messages = [{"role": "user", "content": question}]
msg = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS, temperature=0, seed=42).choices[0].message
# 모델의 응답(도구 호출 요청)을 그대로 대화에 덧붙인다
messages.append({"role": "assistant", "content": msg.content or "", "tool_calls": [tc.model_dump() for tc in msg.tool_calls]})
for tc in msg.tool_calls:
    args = json.loads(tc.function.arguments)              # 인자는 JSON 글자로 온다
    result = safe_calc(args["expression"])                  # 우리 코드가 실행한다
    print("실행:", tc.function.name, args, "→", result)
    messages.append({"role": "tool", "tool_call_id": tc.id, "content": json.dumps({"결과": result})})
print("쌓인 메시지:", [m["role"] for m in messages])
출력
실행: calculate {'expression': '48*1375'} → 66000
쌓인 메시지: ['user', 'assistant', 'tool']

3단계. 최종 답 받기와 도구 없이 견주기

Python
final = client.chat.completions.create(model=MODEL, messages=messages, tools=TOOLS, temperature=0, seed=42).choices[0].message
print("도구를 쓴 답:", final.content)

# 같은 질문을 도구 없이: 모델이 머릿속으로 계산한다
plain = client.chat.completions.create(model=MODEL, messages=[{"role": "user", "content": question}], temperature=0, seed=42)
print("도구 없는 답:", plain.choices[0].message.content.replace("\n", " ")[:120])
출력
도구를 쓴 답: 한 상자에 48개씩 들어있는 사과가 1,375개의 상자라면, 총 사과는 66,000개입니다.
도구 없는 답: 한 상자에 48개의 사과가 들어있는데, 1,375 상자가 있습니다. 이 사과의 총 개수는 다음과 같이 계산할 수 있습니다:  1 상자당 사과 개수 x 상자 수 = 총 사과 개수 48개 x 1,375상자 = 65,00

이 실습이 뜻하는 것

이 실습은 비서가 내민 쪽지를 받아 우리가 계산기를 두드리고, 결과를 다시 건네는 일과 같습니다. 문제와 정답을 미리 적어 둡니다(1단계). 비서가 "48*1375 를 계산해 달라"는 쪽지를 주면 계산기로 66000 을 얻고, 대화 기록에 질문, 쪽지, 결과를 차례로 붙입니다(2단계). 그 기록을 다시 보여 주면 비서가 결과를 문장으로 다듬어 답합니다. 같은 문제를 계산기 없이 암산하게도 해 봅니다(3단계). 여기서 대화 기록이 messages, 결과를 건네는 쪽지가 tool 메시지, 어느 쪽지에 대한 답인지 알려 주는 번호표가 tool_call_id 입니다.

단계출력뜻
1. 정답6600048 × 1375 를 파이썬이 계산한 값입니다. 모델의 답을 견줄 기준입니다
2. 실행calculate {'expression': '48*1375'} → 66000모델이 식을 바르게 세웠고, 계산은 우리 코드의 safe_calc 가 했습니다
2. 쌓인 메시지['user', 'assistant', 'tool']질문, 모델의 도구 요청, 도구 결과가 차례로 쌓였습니다. 이 셋이 있어야 모델이 앞뒤 사정을 압니다
3. 도구를 쓴 답"총 사과는 66,000개입니다"정답과 같습니다. 계산은 코드가, 문장은 모델이 맡았습니다
3. 도구 없는 답"48개 x 1,375상자 = 65,00…"식은 맞게 세웠지만 암산 결과가 정답과 다릅니다. 출력이 120글자에서 잘려 끝자리는 보이지 않습니다

한 줄로 줄이면, 자릿수가 많은 계산은 모델이 틀리기 쉽고, 계산을 도구에 맡기면 바로잡힌다는 것을 한 문제로 보인 실습입니다. 한 문제의 결과라 "도구 없이는 늘 틀린다"는 뜻은 아닙니다. 다음 실습 3에서는 도구를 여러 개 주고 모델이 고르게 합니다.

코드 설명

  • 모델은 48*1375 를 요청했고, safe_calc 가 66000 을 계산했습니다. 대화에는 user, assistant(도구 호출), tool(결과) 세 메시지가 쌓였습니다.
  • 모델의 응답(msg)을 tool_calls 와 함께 대화에 그대로 덧붙여야, 다음 요청에서 모델이 "내가 계산기를 불렀고 이것이 결과다"를 압니다. tc.model_dump() 는 응답 객체를 사전으로 바꿉니다.
  • 결과를 받은 모델은 "총 사과는 66,000개입니다"라고 답했습니다. 계산은 코드가, 문장은 모델이 맡았습니다.
  • 도구 없이 물으면 모델이 식은 바르게 세우지만 "48개 x 1,375상자 = 65,00…"처럼 머릿속 계산에서 정답(66,000)과 다른 값을 냅니다. 언어 모델은 다음 토큰을 고르는 방식이라 자릿수가 많은 계산에 약합니다. 계산을 도구에 맡기는 까닭입니다.
← 이전 절9장 · 실습 1 — 도구 하나 붙이기다음 절 →9장 · 실습 3 — 도구 여러 개에서 고르기