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

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

실습 1 — 도구 하나 붙이기

Ollama 의 qwen2.5:3b 에 계산기 도구 하나를 주고, 모델이 언제 도구를 부르는지 봅니다. Ollama 와 qwen2.5:3b(6장에서 받은 것)가 있어야 합니다. 이 실습은 모델의 요청만 보고, 실행은 실습 2에서 합니다.

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"

# 본문 "에이전트의 안전장치"의 계산기: eval 을 쓰지 않고 사칙연산만 계산한다
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"))

# 모델에게 보여 줄 도구 설명(JSON 스키마)
TOOLS = [{"type": "function", "function": {
    "name": "calculate",
    "description": "사칙연산 식을 계산한다. 숫자 계산이 필요하면 반드시 이 도구를 쓴다.",
    "parameters": {"type": "object",
                   "properties": {"expression": {"type": "string", "description": "계산할 식. 예: 1234*5678"}},
                   "required": ["expression"]}}}]
print(safe_calc("1234*5678"))
출력
7006652

2단계. 도구가 필요한 질문

Python
resp = client.chat.completions.create(model=MODEL, messages=[{"role": "user", "content": "1234 곱하기 5678 은 얼마예요?"}],
                                      tools=TOOLS, temperature=0, seed=42)
msg = resp.choices[0].message
print("글(content):", repr(msg.content))
for tc in msg.tool_calls or []:
    # 도구 이름과 인자(JSON 글자), 그리고 결과를 돌려줄 때 쓸 id
    print("도구:", tc.function.name, "· 인자:", tc.function.arguments, "· id:", tc.id[:12])
print("멈춘 까닭:", resp.choices[0].finish_reason)
출력
글(content): ''
도구: calculate · 인자: {"expression":"1234*5678"} · id: call_c5er4tc
멈춘 까닭: tool_calls

3단계. 도구가 필요 없는 질문

Python
for q in ["안녕하세요, 오늘 기분 어때요?", "세탁기와 건조기의 차이를 한 문장으로 알려 주세요.", "387 더하기 1,250 에서 99 를 빼면?"]:
    msg = client.chat.completions.create(model=MODEL, messages=[{"role": "user", "content": q}],
                                         tools=TOOLS, temperature=0, seed=42).choices[0].message
    calls = [(tc.function.name, tc.function.arguments) for tc in msg.tool_calls or []]
    print(q, "→", calls if calls else "도구 없이 답: " + (msg.content or "")[:40].replace("\n", " "))
출력
안녕하세요, 오늘 기분 어때요? → 도구 없이 답: 안녕하세요! 저는 인공지능으로, 기분을 느끼는 것은 아니지만, 도와드릴
세탁기와 건조기의 차이를 한 문장으로 알려 주세요. → 도구 없이 답: 세탁기는衣物를 청결하게 하며, 건조기는 청결한衣物를 건조시키는 기계입니다
387 더하기 1,250 에서 99 를 빼면? → [('calculate', '{"expression":"387 + 1250 - 99"}')]

이 실습이 뜻하는 것

이 실습은 계산기를 옆에 둔 비서에게 일을 맡기는 장면과 같습니다. 먼저 계산기를 만들고, "사칙연산 식을 넣으면 답을 준다"는 사용 설명서를 써 둡니다(1단계). 비서에게 곱셈을 물으면 직접 답하지 않고 "계산기에 1234*5678 을 넣어 주세요"라는 쪽지를 내밉니다(2단계). 인사나 상식 질문에는 계산기 없이 바로 답하고, 덧셈과 뺄셈이 섞인 질문에는 다시 쪽지를 씁니다(3단계). 여기서 비서가 qwen2.5:3b, 계산기가 safe_calc, 사용 설명서가 TOOLS 의 도구 설명(JSON 스키마, JSON 은 JavaScript Object Notation), 쪽지가 tool_calls 입니다. 비유와 다른 점은, 이 실습의 비서는 쪽지만 내밀 뿐 계산기를 직접 누르지 못한다는 것입니다. 계산기를 눌러 결과를 돌려주는 일은 실습 2에서 우리 코드가 합니다.

단계출력뜻
1. 계산기 확인7006652safe_calc("1234*5678") 가 바르게 계산합니다. 모델을 부르기 전에 도구부터 제대로 도는지 본 것입니다
2. 글(content)''곱셈 질문에 모델은 글로 된 답을 한 글자도 쓰지 않았습니다
2. 도구calculate, 인자 {"expression":"1234*5678"}대신 "계산기에 이 식을 넣어 달라"는 요청을 보냈습니다. 인자는 JSON 꼴의 글자입니다
2. 멈춘 까닭tool_calls답을 다 써서가 아니라, 도구를 불러 달라고 해서 멈췄다는 표시입니다
3. 인사도구 없이 답계산이 필요 없는 말에는 도구를 부르지 않았습니다
3. 세탁기와 건조기도구 없이 답, "衣物" 섞임이것도 도구 없이 답했습니다. 작은 Qwen 모델은 한국어 답에 중국어를 섞기도 합니다
3. 덧셈과 뺄셈calculate, 387 + 1250 - 99말로 된 계산을 식으로 옮기고, "1,250"의 쉼표도 지워 적었습니다

한 줄로 줄이면, 도구 설명만 주면 모델이 "계산이 필요한 때"를 스스로 가려 도구를 부른다는 것을 보인 실습입니다. 아직 계산기를 실제로 돌리지 않았으므로 최종 답은 없습니다. 결과를 돌려주고 답을 받는 일은 실습 2에서 합니다. 질문 네 개로 본 것이라, 모델이 늘 알맞게 고른다는 뜻은 아닙니다.

코드 설명

  • tools=TOOLS 를 주면 모델은 글로 답하거나, 답 대신 tool_calls 를 돌려줍니다. 본문 "도구 호출의 흐름"의 2번입니다.
  • 곱셈 질문에는 글(content)이 빈 글자이고, tool_calls 에 calculate 와 인자 {"expression":"1234*5678"} 이 담겼습니다. finish_reason 이 tool_calls 인 것은 "답을 다 써서"가 아니라 "도구를 불러 달라고 해서" 멈췄다는 뜻입니다.
  • 인사와 세탁기 질문에는 도구 없이 글로 답하고, "387 더하기 1,250 에서 99 를 빼면?"에는 387 + 1250 - 99 로 식을 바꿔 계산기를 불렀습니다. 쉼표가 든 숫자도 식으로 옮겨 적었습니다. 세탁기 답에 "衣物" 같은 한자가 섞인 것처럼, 작은 Qwen 모델은 한국어 답에 중국어를 섞기도 합니다.
  • 계산기는 eval 대신 ast 로 만든 safe_calc 를 씁니다. 모델이 넘긴 식을 그대로 실행하지 않기 위해서입니다.
← 이전 절9장 · 실습 지도다음 절 →9장 · 실습 2 — 도구 결과를 돌려주고 답 받기