research

Tool Call 판단 주체 — 모델이 결정한다

Tool Call 판단 주체 — 모델이 결정한다

llama.cpp 서버에서 채팅 요청이 들어왔을 때, "이게 tool을 쓰는 요청인가?"를 판단하는 건 서버 로직이 아니라 모델 자체다.

동작 흐름

1. 클라이언트가 tools를 포함해서 요청

{
  "messages": [...],
  "tools": [
    {
      "type": "function",
      "function": {
        "name": "search_web",
        "description": "웹을 검색한다",
        "parameters": {...}
      }
    }
  ]
}

2. llama.cpp 서버가 Chat Template에 tools를 주입

모델의 tokenizer config에는 Jinja 기반 chat template이 있다. 서버는 tools 필드를 받으면 이를 시스템 프롬프트 영역에 삽입한다.

모델이 실제로 보는 텍스트는 이런 형태:

<|im_start|>system
You are a helpful assistant.

# Tools
You may call one or more functions...
{tools 정의 JSON}
<|im_end|>
<|im_start|>user
오늘 날씨 어때?
<|im_end|>
<|im_start|>assistant

3. 모델이 next token prediction으로 결정

이후는 순수하게 모델의 언어 생성. 입력 컨텍스트(질문 내용 + tools 정의)를 바탕으로:

  • tool이 필요하다고 판단 → <tool_call> 토큰으로 시작하는 JSON 생성
  • 아니면 → 일반 텍스트 생성

4. 서버가 출력을 파싱해서 응답 포맷 결정

생성 완료 후 서버가 출력물을 확인:

  • <tool_call> 패턴 있음 → finish_reason: "tool_calls" + tool_calls 파싱 결과 반환
  • 없음 → finish_reason: "stop" + 텍스트 반환

tools를 안 보내면?

// tools 필드 없음
{
  "messages": [...]
}

Chat template이 tools를 주입하지 않으므로, 모델은 tool의 존재 자체를 모른다. tool call이 일어날 수 없다.

브라우저 UI vs 직접 API 호출

| 클라이언트 | tools 포함 여부 | tool call 가능 | |------------|-----------------|----------------| | llama.cpp 기본 웹 UI | ❌ (미구현) | ❌ | | curl / Python 직접 호출 | ✅ (내가 넣으면) | ✅ | | tool call 지원 UI (Claude.ai 등) | ✅ (UI가 처리) | ✅ |

→ tool call 여부는 포트나 클라이언트 종류가 아니라, 요청에 tools를 넣느냐의 문제.

클라이언트가 져야 하는 두 가지 책임

tool call을 "사용"한다는 건 클라이언트가 두 가지를 모두 처리해야 한다는 뜻이다:

  1. 요청 시: tools 정의를 포함해서 보내기
  2. 응답 후: finish_reason: "tool_calls" 감지 → tool 실행 → 결과를 messages에 추가 → 재요청

서버(llama.cpp)는 이 루프에서 아무것도 해주지 않는다. 신호만 줄 뿐이고, 실제 실행과 루프는 전부 클라이언트 몫.

브라우저 UI:    요청 → 텍스트 응답 표시. 끝.

tool_calls.py:  요청 (tools 포함)
                  → finish_reason: tool_calls 감지
                  → search_web() 직접 실행
                  → 결과를 messages에 추가
                  → 재요청
                  → 최종 텍스트 응답

관련