File size: 8,578 Bytes
38a8c2a
f58ffad
 
 
 
8f2759e
 
 
f58ffad
 
 
 
38a8c2a
8f2759e
f58ffad
8f2759e
f58ffad
8f2759e
f58ffad
 
 
 
5cce7a2
8f2759e
f58ffad
8f2759e
f58ffad
8f2759e
f58ffad
 
 
 
 
 
8f2759e
f58ffad
8f2759e
f58ffad
 
 
8f2759e
f58ffad
 
 
 
 
8f2759e
f58ffad
2fe38eb
f58ffad
2fe38eb
f58ffad
2fe38eb
f58ffad
 
 
 
 
 
 
 
 
 
 
 
2fe38eb
f58ffad
2fe38eb
f58ffad
 
 
 
 
2fe38eb
f58ffad
2fe38eb
f58ffad
92c7b2b
f58ffad
92c7b2b
f58ffad
 
 
 
 
92c7b2b
f58ffad
92c7b2b
f58ffad
78bce11
f58ffad
 
 
 
 
78bce11
f58ffad
92c7b2b
f58ffad
2fe38eb
f58ffad
 
 
2fe38eb
f58ffad
 
2fe38eb
f58ffad
 
 
8f2759e
d56bcf7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
f58ffad
8f2759e
f58ffad
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
---
base_model: meta-llama/Llama-3.2-1B-Instruct
language:
  - ko
  - en
library_name: peft
pipeline_tag: text-generation
tags:
  - function-calling
  - tool-use
  - korean
  - qlora
---

# toolRouter-1B

ν•œκ΅­μ–΄ μš°μ„  **툴콜 λΌμš°ν„°** β€” μ‚¬μš©μž λ°œν™”λ₯Ό λ°›μ•„ 항상 툴콜 ν•˜λ‚˜λ‘œ μ‘λ‹΅ν•˜λŠ” λΌμš°νŒ… λͺ¨λΈμž…λ‹ˆλ‹€. **Llama-3.2-1B-Instruct** μœ„μ— μ–ΉλŠ” QLoRA μ–΄λŒ‘ν„°(~45MB)둜, 4bit λ‘œλ“œ μ‹œ VRAM μ•½ 2.5GB, GGUF Q4 λ³€ν™˜ μ‹œ μ•½ 1GB둜 κ΅¬λ™λ©λ‹ˆλ‹€.

- 자유 ν…μŠ€νŠΈλ₯Ό μƒμ„±ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. λͺ¨λ“  좜λ ₯은 `<tool_call>{"name": ..., "arguments": {...}}</tool_call>` ν˜•μ‹μž…λ‹ˆλ‹€.
- 일반 λŒ€ν™”Β·μƒμ‹ 응닡은 `reply(text=...)`, 처리 λΆˆκ°€ μš”μ²­μ€ `escalate(query=원문)` 툴둜 ν‘œν˜„ν•©λ‹ˆλ‹€.
- μΈμžλŠ” μ‚¬μš©μž λ¬Έμž₯의 ν‘œν˜„μ„ 원문 κ·ΈλŒ€λ‘œ μΆ”μΆœν•˜λ„λ‘ ν•™μŠ΅λμŠ΅λ‹ˆλ‹€ (ν•œκ΅­μ–΄ 인자의 μ˜μ–΄ λ²ˆμ—­Β·μš”μ•½Β·μ§€μ–΄λƒ„ μ–΅μ œ).
- 처음 λ³΄λŠ” 툴 μŠ€ν‚€λ§ˆ(zero-shot toolset)μ—μ„œλ„ λ™μž‘ν•˜λ„λ‘ ν•™μŠ΅λμŠ΅λ‹ˆλ‹€.
- 멀티턴이 ν•„μš”ν•˜λ©΄ [recallResolver-1B](https://huggingface.co/kimjg/recallResolver-1B)와 짝으둜 μ“°μ„Έμš” β€” 같은 베이슀λ₯Ό κ³΅μœ ν•΄ μ–΄λŒ‘ν„° μ „ν™˜λ§ŒμœΌλ‘œ ν•¨κ»˜ λ™μž‘ν•˜λ©°, λͺ¨λΈμ„ λ”°λ‘œ λ„μš°λŠ” 것 λŒ€λΉ„ VRAM이 μ ˆλ°˜μž…λ‹ˆλ‹€ β€” μ‹€μΈ‘: llama.cpp 베이슀 Q4 + LoRA 2개 ν•œ μ„œλ²„ = **1.5GB**, λ¨Έμ§€ Q4 μ„œλ²„ 2개 = 2.8GB (각 2048 ctx, μΆ”λ‘  버퍼 포함).

## μ΅œμ‹  버전 β€” 2026-09.2 (recall 지원)

ν•œκ΅­μ–΄ μ‹€μ‚¬μš© 문체 6,000λ¬Έμž₯ 직접 μž‘μ„± λ°μ΄ν„°λ‘œ κ°•ν™”ν•˜κ³ , **κ³Όκ±° λŒ€ν™” μ°Έμ‘° 감지(`recall`)**λ₯Ό μΆ”κ°€ν•œ λ²„μ „μž…λ‹ˆλ‹€.

| 평가 | 수치 |
|---|---|
| ν•œκ΅­μ–΄ 평가셋 396 (decision / tool / args) | 97.2 / 95.7 / 83.8 |
| held-out 1,100 (decision / tool / args) | 99.5 / 99.2 / 97.0 |
| BFCL v3 single-turn (simple / multiple / irrelevance / overall) | 86.0 / 85.5 / 80.0 / **84.2** |
| recall νŒμ • (경계 μΌ€μ΄μŠ€ 포함 120) | μž¬ν˜„ 86.7% / μ •λ°€ 91.2% / 함정 λ°©μ–΄ 52/55 |

## μ‚¬μš©λ²•

```python
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import PeftModel

base = "meta-llama/Llama-3.2-1B-Instruct"
model = AutoModelForCausalLM.from_pretrained(base, device_map="auto")
model = PeftModel.from_pretrained(model, "kimjg/toolRouter-1B")
tokenizer = AutoTokenizer.from_pretrained("kimjg/toolRouter-1B")
```

μ±„νŒ… ν…œν”Œλ¦Ώμ€ μ €μž₯μ†Œμ˜ `chat_template.jinja`에 포함돼 μžˆμŠ΅λ‹ˆλ‹€. greedy 디코딩을 ꢌμž₯ν•˜λ©°, ν”„λ‘œλ•μ…˜μ—μ„œλŠ” xgrammar λ“± JSON μŠ€ν‚€λ§ˆ 문법 κ°•μ œ(constrained decoding)λ₯Ό ν•¨κ»˜ μ“°λ©΄ νŒŒμ‹±Β·μŠ€ν‚€λ§ˆ ν†΅κ³Όμœ¨ 100%λ₯Ό 보μž₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

## 툴 μ§€μ • 방법

μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈμ— 툴 μŠ€ν‚€λ§ˆλ₯Ό **ν•œ 쀄에 ν•˜λ‚˜μ”© compact JSON**으둜 λ‚˜μ—΄ν•©λ‹ˆλ‹€. ν•™μŠ΅ λ•Œ μ‚¬μš©ν•œ ν…œν”Œλ¦Ώ κ·ΈλŒ€λ‘œ μ“°λŠ” 것을 ꢌμž₯ν•©λ‹ˆλ‹€:

```text
λ„ˆλŠ” κ²Œμž„ 곡랡 λ„μš°λ―Έμ˜ 툴콜 λΌμš°ν„°λ‹€. μ‚¬μš©μžμ˜ μš”μ²­μ„ 읽고 μ•„λž˜ 툴 쀑 μ •ν™•νžˆ ν•˜λ‚˜λ₯Ό ν˜ΈμΆœν•˜λŠ” κ²ƒμœΌλ‘œλ§Œ μ‘λ‹΅ν•œλ‹€. 자유 ν…μŠ€νŠΈλ₯Ό 좜λ ₯ν•˜μ§€ μ•ŠλŠ”λ‹€.
- 일반 λŒ€ν™”λ‚˜ 직접 λ‹΅ν•  수 μžˆλŠ” μ§ˆλ¬Έμ€ reply νˆ΄μ„ ν˜ΈμΆœν•œλ‹€.
- λͺ©λ‘μ— μžˆλŠ” 툴둜 μ²˜λ¦¬ν•  수 μ—†κ±°λ‚˜ λ²”μœ„λ₯Ό λ²—μ–΄λ‚œ μš”μ²­μ€ escalate νˆ΄μ„ ν˜ΈμΆœν•œλ‹€.
- 툴 μΈμžλŠ” μ‚¬μš©μžμ˜ μš”μ²­μ—μ„œ μΆ”μΆœν•œλ‹€. μš”μ²­μ— μ—†λŠ” 값을 μ§€μ–΄λ‚΄μ§€ μ•ŠλŠ”λ‹€.
응닡 ν˜•μ‹μ€ λ°˜λ“œμ‹œ λ‹€μŒκ³Ό κ°™λ‹€:
<tool_call>{"name": "<툴 이름>", "arguments": {<인자>}}</tool_call>
μ‚¬μš© κ°€λŠ₯ν•œ 툴 λͺ©λ‘:
<tools>
{tools_json}
</tools>
```

`{tools_json}` μžλ¦¬μ— λ“€μ–΄κ°€λŠ” 툴 μŠ€ν‚€λ§ˆ ν˜•μ‹ (OpenAI function μŠ€ν‚€λ§ˆμ™€ λ™μΌν•œ `parameters` ꡬ쑰):

```json
{"name": "get_weather", "description": "μ§€μ—­μ˜ ν˜„μž¬ 날씨λ₯Ό μ‘°νšŒν•œλ‹€", "parameters": {"type": "object", "properties": {"location": {"type": "string", "description": "λ„μ‹œ 이름"}, "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]}}, "required": ["location"]}}
{"name": "reply", "description": "툴이 ν•„μš” μ—†λŠ” μ§ˆλ¬Έμ— 직접 λ‹΅ν•œλ‹€", "parameters": {"type": "object", "properties": {"text": {"type": "string"}}, "required": ["text"]}}
{"name": "escalate", "description": "μ²˜λ¦¬ν•  수 μ—†λŠ” μš”μ²­μ„ μƒμœ„λ‘œ λ„˜κΈ΄λ‹€", "parameters": {"type": "object", "properties": {"query": {"type": "string"}}, "required": ["query"]}}
```

`reply`와 `escalate`λŠ” 항상 λͺ©λ‘μ— 포함해야 ν•©λ‹ˆλ‹€. λ‚˜λ¨Έμ§€ νˆ΄μ€ 자유둭게 ꡐ체할 수 있고, ν•™μŠ΅ λ•Œ λ³Έ 적 μ—†λŠ” νˆ΄μ…‹λ„ λ™μž‘ν•©λ‹ˆλ‹€(zero-shot). 툴 μ„€λͺ…은 μ˜μ–΄Β·ν•œκ΅­μ–΄ λͺ¨λ‘ μ§€μ›ν•©λ‹ˆλ‹€.

## κΈ°λ³Έ 툴 3μ’… (λΌμš°ν„°μ˜ ν”„λ‘œν† μ½œ)

이 λΌμš°ν„°λŠ” "λͺ¨λ“  응닡이 툴콜"μ΄λΌλŠ” κ·œμ•½ μœ„μ—μ„œ λ™μž‘ν•˜λ©°, 도메인 툴 외에 λ‹€μŒ κΈ°λ³Έ 툴둜 λͺ¨λ“  λ°œν™”λ₯Ό μ»€λ²„ν•©λ‹ˆλ‹€:

| κΈ°λ³Έ 툴 | μ–Έμ œ λ‚˜μ˜€λ‚˜ | 인자 | μ„œλ²„κ°€ ν•  일 |
|---|---|---|---|
| `reply` | 툴이 ν•„μš” μ—†λŠ” μΈμ‚¬Β·μž‘λ‹΄Β·μƒμ‹ 질문 ("κ³ λ§ˆμ›Œ!", "μ•ˆλ…•") | `text`: 짧은 ν•œκ΅­μ–΄ λ‹΅λ³€ | textλ₯Ό κ·ΈλŒ€λ‘œ μ‚¬μš©μžμ—κ²Œ |
| `escalate` | λͺ©λ‘μ˜ 툴둜 처리 λΆˆκ°€ν•œ μš”μ²­ β€” λ‹€λ₯Έ 도메인, 볡합 μž‘μ—…, ν•„μˆ˜ 인자λ₯Ό μ•Œ 수 μ—†μŒ, κΈ΄ κΈ€μ“°κΈ° λ“± | `query`: **μ‚¬μš©μž μš”μ²­ 원문 κ·ΈλŒ€λ‘œ** | μƒμœ„ λͺ¨λΈΒ·λ‹΄λ‹Ήμžμ—κ²Œ 전달 |
| `recall` | λ°œν™”κ°€ κ³Όκ±° λŒ€ν™”μ˜ 값을 가리킬 λ•Œ ("κ·Έκ±° 재고 μ–Όλ§ˆ λ‚¨μ•˜μ–΄?", "μ•„κΉŒ κ·Έ λ„μ‹œ 내일 λ‚ μ”¨λŠ”?") | `needs`: μ°Ύμ•„μ•Ό ν•  κ°’μ˜ ν•œκ΅­μ–΄ μ„€λͺ… | [recallResolver-1B](https://huggingface.co/kimjg/recallResolver-1B)둜 νžˆμŠ€ν† λ¦¬μ—μ„œ 값을 μ°Ύμ•„ λ°œν™”λ₯Ό 보강 ν›„ λΌμš°ν„° 재호좜 |

섀계 μ˜λ„: λΌμš°ν„°λŠ” 자유 ν…μŠ€νŠΈλ₯Ό μƒμ„±ν•˜μ§€ μ•ŠμœΌλ―€λ‘œ "λŒ€λ‹΅ν•œλ‹€/λ„˜κΈ΄λ‹€/κ³Όκ±°λ₯Ό μ°ΎλŠ”λ‹€"κΉŒμ§€ μ „λΆ€ 툴콜둜 ν‘œν˜„λ©λ‹ˆλ‹€. 덕뢄에 λ‹€μš΄μŠ€νŠΈλ¦Όμ€ λΆ„κΈ° 처리만 ν•˜λ©΄ 되고, 좜λ ₯ 문법 κ°•μ œ(constrained decoding)λ₯Ό 전체 응닡에 μ μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

**reply 운용 팁**: `reply.text`λ₯Ό κ·ΈλŒ€λ‘œ μ“Έ μˆ˜λ„ μžˆμ§€λ§Œ(μ§€μ—° μ΅œμ†Œ), replyλ₯Ό **"λŒ€ν™” νŠΈλž™" μ‹ ν˜Έλ‘œλ§Œ μ“°κ³  λ°œν™”λ₯Ό 별도 μ±— λͺ¨λΈλ‘œ λ„˜κΈ°λŠ”** ꡬ성도 λΆ„κΈ° ν•œ 쀄이면 λ©λ‹ˆλ‹€. 페λ₯΄μ†Œλ‚˜ μžˆλŠ” 본격 챗봇과 뢙일 λ•Œ μœ μš©ν•©λ‹ˆλ‹€:

```python
call = route(utterance)
if call["name"] == "reply":
    return chat_model(persona, utterance)  # textλŠ” 버리고 큰 λͺ¨λΈμ΄ λŒ€ν™”
```

> **recall 연동 팁** (recallResolver-1Bμ™€μ˜ end-to-end κ²€μ¦μ—μ„œ ν™•μ •ν•œ μ„œλ²„ κ·œμΉ™): ①리쑸버가 값을 찾으면 κ·Έ κ°’μ˜ 좜처 인자 μŠ€ν‚€λ§ˆ μ„€λͺ…을 라벨둜 λ°œν™”λ₯Ό 보강해 μž¬λΌμš°νŒ… β€” `"{λ°œν™”} ({라벨}: {κ°’})"` β‘‘λΌμš°ν„°μ˜ needs둜 리쑸버가 λͺ» 찾으면 ν•΄λ‹Ή 기둝의 인자 μ„€λͺ…λ“€λ‘œ 재질의 β‘’needs와 κ°’ 라벨의 μœ ν˜•μ΄ μ•ˆ 맞으면 μžλ™ μ£Όμž… λŒ€μ‹  μ‚¬μš©μžμ—κ²Œ 확인. 재호좜이 또 recall이면 escalate(루프 κ°€λ“œ). recall을 μ“°μ§€ μ•ŠμœΌλ €λ©΄ 툴 λͺ©λ‘μ—μ„œ λΉΌλ©΄ λ©λ‹ˆλ‹€ β€” 그러면 κ³Όκ±° μ°Έμ‘° λ°œν™”λŠ” escalate둜 λ‚˜μ˜΅λ‹ˆλ‹€.

λ™μž‘ 예:

```text
user: μš°μ‚° 챙겨야 ν•˜λ‚˜, λŒ€μ „μΈλ°
assistant: <tool_call>{"name": "get_weather", "arguments": {"location": "λŒ€μ „"}}</tool_call>

user: κ³ λ§ˆμ›Œ!
assistant: <tool_call>{"name": "reply", "arguments": {"text": "λ³„λ§μ”€μ„μš”, 또 ν•„μš”ν•˜λ©΄ 뢈러 μ£Όμ„Έμš”!"}}</tool_call>

user: 이 μ‚¬μ§„μ—μ„œ κΈ€μž μ’€ μ½μ–΄μ€˜
assistant: <tool_call>{"name": "escalate", "arguments": {"query": "이 μ‚¬μ§„μ—μ„œ κΈ€μž μ’€ μ½μ–΄μ€˜"}}</tool_call>
```

## GGUF (llama.cpp)

μ–΄λŒ‘ν„°λ₯Ό λ¨Έμ§€ν•΄ μ–‘μžν™”ν•œ 단일 νŒŒμΌλ„ μ œκ³΅ν•©λ‹ˆλ‹€. 손싀은 자체 평가셋 μ‹€μΈ‘μΉ˜μž…λ‹ˆλ‹€:

| 파일 | 크기 | ν•œκ΅­μ–΄ 평가셋 (decision/tool/args) | ꢌμž₯ μš©λ„ |
|---|---|---|---|
| `toolRouter-1B-Q8_0.gguf` | 1.3GB | 96.5 / 95.0 / 83.4 (원본 λŒ€λΉ„ ~-0.8%p) | ν’ˆμ§ˆ μš°μ„  |
| `toolRouter-1B-Q4_K_M.gguf` | 0.8GB | 95.5 / 94.2 / 83.4 (~-1.6%p) | μ΅œμ†Œ VRAM |

```bash
llama-server -m toolRouter-1B-Q8_0.gguf -ngl 99 -c 2048
```

μ±„νŒ… ν…œν”Œλ¦Ώμ΄ GGUF에 포함돼 μžˆμŠ΅λ‹ˆλ‹€. greedy(temperature 0) ꢌμž₯, 좜λ ₯은 `<tool_call>...</tool_call>` ν˜•μ‹ κ·ΈλŒ€λ‘œμž…λ‹ˆλ‹€. recall νŒμ •μ€ μ–‘μžν™”μ— λ‹€μ†Œ λ―Όκ°ν•˜λ‹ˆ(경계 μΌ€μ΄μŠ€ -4~5%p) recall을 μ“°λŠ” νŒŒμ΄ν”„λΌμΈμ΄λ©΄ Q8을 κΆŒν•©λ‹ˆλ‹€.

## μ œν•œ

- 단일 ν„΄ λΌμš°νŒ… μ „μš©μž…λ‹ˆλ‹€ (λ©€ν‹°ν„΄ λ¬Έλ§₯ μ°Έμ‘°λŠ” 미지원).
- 세계지식 좔둠이 ν•„μš”ν•œ 인자 보정(예: λ„μ‹œλͺ…β†’ν†΅ν™”μ½”λ“œ), μƒλŒ€ λ‚ μ§œ 해석은 λ‹€μš΄μŠ€νŠΈλ¦Όμ—μ„œ μ²˜λ¦¬ν•˜μ„Έμš”. μƒλŒ€ λ‚ μ§œλŠ” μ‹œμŠ€ν…œ ν”„λ‘¬ν”„νŠΈμ— ν˜„μž¬ λ‚ μ§œλ₯Ό μ œκ³΅ν•˜λ©΄ κ°œμ„ λ©λ‹ˆλ‹€.
- base λͺ¨λΈμ˜ Llama 3.2 Community Licenseλ₯Ό λ”°λ¦…λ‹ˆλ‹€.