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

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

실습 3 — 도구 여러 개에서 고르기

계산기, 사내 규정 검색(7장의 company_rules.txt), 오늘 날짜 도구 셋을 주고, 질문마다 모델이 어떤 도구를 고르는지 봅니다. 검색은 7장처럼 임베딩 모델을 CPU 에 두고 씁니다.

1단계. 세 도구 만들기

Python
import ast, json, operator, re
from datetime import date
import numpy as np
from sentence_transformers import SentenceTransformer
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"))

# 사내 규정 검색 도구: 7장의 규정집을 조항 단위로 나눠 의미 검색한다
rules = [l for l in open("company_rules.txt", encoding="utf-8").read().split("\n") if re.match(r"^제\d+조\(", l)]
emb = SentenceTransformer("intfloat/multilingual-e5-small", device="cpu")
R = emb.encode(["passage: " + r for r in rules], normalize_embeddings=True)
def search_rules(query):
    q = emb.encode("query: " + query, normalize_embeddings=True)
    return [rules[i] for i in np.argsort(-(R @ q))[:2]]       # 가까운 조항 2개

def get_today():
    return "2026-10-27"          # 교안의 '오늘'. 실제 서비스라면 date.today().isoformat()

IMPL = {"calculate": lambda expression: safe_calc(expression), "search_rules": search_rules, "get_today": get_today}
def tool(name, desc, props=None, req=None):
    return {"type": "function", "function": {"name": name, "description": desc,
            "parameters": {"type": "object", "properties": props or {}, "required": req or []}}}
TOOLS = [tool("calculate", "사칙연산 식을 계산한다. 숫자 계산이 필요하면 반드시 이 도구를 쓴다.",
              {"expression": {"type": "string", "description": "계산할 식. 예: 5*12"}}, ["expression"]),
         tool("search_rules", "솔내기술(가상 회사)의 사내 규정(근무, 휴가, 출장, 경비, 복지, 교육, 보안)을 찾는다.",
              {"query": {"type": "string", "description": "찾을 내용. 예: 회식비 한도"}}, ["query"]),
         tool("get_today", "오늘 날짜를 YYYY-MM-DD 로 돌려준다.")]
print(len(rules), "조항 ·", search_rules("재택 근무")[0][:40])
출력
30 조항 · 제3조(재택 근무) 일주일에 이틀까지 재택 근무를 할 수 있습니다. 재택

2단계. 질문마다 고른 도구

Python
SYSTEM = "당신은 솔내기술(가상 회사)의 사내 도우미입니다. 필요하면 도구를 쓰고, 도구 없이 알 수 있으면 바로 답하세요."
questions = ["37 곱하기 48 은?", "재택 근무는 일주일에 며칠까지 돼요?", "오늘이 며칠이에요?",
             "안녕하세요!", "팀원 6명이 회식하면 회식비는 최대 얼마까지 쓸 수 있어요?", "해외 출장을 3일 가면 일비는 모두 몇 달러예요?"]
for q in questions:
    msg = client.chat.completions.create(model=MODEL, messages=[{"role": "system", "content": SYSTEM}, {"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(f"{q:32s} →", calls if calls else "도구 없음")
출력
37 곱하기 48 은?                     → [('calculate', '{"expression":"37*48"}')]
재택 근무는 일주일에 며칠까지 돼요?             → [('search_rules', '{"query":"재택 근무"}')]
오늘이 며칠이에요?                       → [('get_today', '{}')]
안녕하세요!                           → 도구 없음
팀원 6명이 회식하면 회식비는 최대 얼마까지 쓸 수 있어요? → [('search_rules', '{"query":"회식비 한도"}')]
해외 출장을 3일 가면 일비는 모두 몇 달러예요?      → [('search_rules', '{"query":"출장 일비 해외 3일"}')]

이 실습이 뜻하는 것

이 실습은 사내 도우미의 책상 위에 계산기, 규정집, 달력 세 가지를 놓아 두는 장면과 같습니다. 세 도구를 준비하고 규정집 찾기가 되는지 확인합니다(1단계). 여섯 질문을 차례로 던지고, 도우미가 어느 도구에 손을 뻗는지 봅니다(2단계). 여기서 계산기가 calculate, 규정집이 search_rules(7장의 규정집을 뜻으로 찾는 검색), 달력이 get_today, 도우미가 qwen2.5:3b 입니다. 비유와 다른 점은, 이 실습에서는 도우미가 처음 손을 뻗는 도구만 보고, 도구를 실제로 써서 답까지 가지는 않는다는 것입니다.

단계출력뜻
1. 조항 수30 조항규정집이 조항 30개로 나뉘었습니다
1. 검색 확인"재택 근무" → 제3조(재택 근무)검색 도구가 맞는 조항을 맨 위로 찾아옵니다
2. 37 곱하기 48calculate, 37*48계산에는 계산기를 골랐습니다
2. 재택 근무search_rules, "재택 근무"규정 질문에는 규정 검색을 골랐습니다
2. 오늘 날짜get_today날짜는 모델이 스스로 알 수 없는 정보라 달력 도구를 불렀습니다
2. 인사도구 없음도구가 필요 없는 말에는 바로 답하려 했습니다
2. 회식비, 해외 출장 일비둘 다 search_rules 만한도를 먼저 알아야 곱할 수 있으니 첫 걸음으로는 맞습니다. 곱셈은 아직 하지 않았습니다

한 줄로 줄이면, 도구 설명에 할 일과 범위를 적어 두면 모델이 질문마다 알맞은 도구를 고른다는 것을 보인 실습입니다. 회식비처럼 "찾고 나서 계산"해야 하는 질문은 한 번의 응답으로 끝나지 않으므로, 결과를 돌려주고 다시 묻는 자율 루프를 실습 4에서 만듭니다. 질문 여섯 개로 본 결과라, 다른 질문에서도 늘 맞게 고른다는 뜻은 아닙니다.

코드 설명

  • 도구 셋은 실제로 하는 일이 전혀 다릅니다. 계산은 코드, 규정은 7장의 검색, 날짜는 함수가 맡고, 모델은 "어느 것을 쓸지"만 정합니다. IMPL 은 도구 이름과 실제 함수를 짝지은 사전으로, 실습 4의 루프가 씁니다.
  • 곱셈에는 calculate, 재택 근무에는 search_rules("재택 근무"), 날짜에는 get_today, 인사에는 도구 없음으로, 넷 모두 알맞은 도구를 골랐습니다.
  • 회식비와 해외 출장 일비 질문은 첫 응답에서 search_rules 만 불렀습니다. 한도를 알아야 곱할 수 있으니 순서가 맞습니다. 다만 한 번의 응답으로는 첫 걸음까지만 가므로, 결과를 돌려주고 다시 묻는 루프가 필요합니다(실습 4).
  • 도구 설명에 "솔내기술(가상 회사)의 사내 규정(근무, 휴가, …)"처럼 범위를 적어 두어야, 모델이 규정과 상관없는 질문에 검색을 부르지 않습니다.
← 이전 절9장 · 실습 2 — 도구 결과를 돌려주고 답 받기다음 절 →9장 · 실습 4 — 자율 루프 만들기