9장. 도구를 쓰는 LLM 과 Agentic Workflow
실습 3 — 도구 여러 개에서 고르기
계산기, 사내 규정 검색(7장의 company_rules.txt), 오늘 날짜 도구 셋을 주고, 질문마다 모델이 어떤 도구를 고르는지 봅니다. 검색은 7장처럼 임베딩 모델을 CPU 에 두고 씁니다.
1단계. 세 도구 만들기
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단계. 질문마다 고른 도구
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 곱하기 48 | calculate, 37*48 | 계산에는 계산기를 골랐습니다 |
| 2. 재택 근무 | search_rules, "재택 근무" | 규정 질문에는 규정 검색을 골랐습니다 |
| 2. 오늘 날짜 | get_today | 날짜는 모델이 스스로 알 수 없는 정보라 달력 도구를 불렀습니다 |
| 2. 인사 | 도구 없음 | 도구가 필요 없는 말에는 바로 답하려 했습니다 |
| 2. 회식비, 해외 출장 일비 | 둘 다 search_rules 만 | 한도를 먼저 알아야 곱할 수 있으니 첫 걸음으로는 맞습니다. 곱셈은 아직 하지 않았습니다 |
한 줄로 줄이면, 도구 설명에 할 일과 범위를 적어 두면 모델이 질문마다 알맞은 도구를 고른다는 것을 보인 실습입니다. 회식비처럼 "찾고 나서 계산"해야 하는 질문은 한 번의 응답으로 끝나지 않으므로, 결과를 돌려주고 다시 묻는 자율 루프를 실습 4에서 만듭니다. 질문 여섯 개로 본 결과라, 다른 질문에서도 늘 맞게 고른다는 뜻은 아닙니다.
코드 설명
- 도구 셋은 실제로 하는 일이 전혀 다릅니다. 계산은 코드, 규정은 7장의 검색, 날짜는 함수가 맡고, 모델은 "어느 것을 쓸지"만 정합니다.
IMPL은 도구 이름과 실제 함수를 짝지은 사전으로, 실습 4의 루프가 씁니다. - 곱셈에는
calculate, 재택 근무에는search_rules("재택 근무"), 날짜에는get_today, 인사에는 도구 없음으로, 넷 모두 알맞은 도구를 골랐습니다. - 회식비와 해외 출장 일비 질문은 첫 응답에서
search_rules만 불렀습니다. 한도를 알아야 곱할 수 있으니 순서가 맞습니다. 다만 한 번의 응답으로는 첫 걸음까지만 가므로, 결과를 돌려주고 다시 묻는 루프가 필요합니다(실습 4). - 도구 설명에 "솔내기술(가상 회사)의 사내 규정(근무, 휴가, …)"처럼 범위를 적어 두어야, 모델이 규정과 상관없는 질문에 검색을 부르지 않습니다.