툴 파서
툴 파서 (Tool Parser)
SGLang의 Function calling 기능을 사용하는 방법을 보여주는 가이드입니다. 모델이 도구 호출(tool call)을 생성했을 때 그 응답을 해석하는 파서를 지정하는 방법을 다룹니다.
출처: 문서
본문
이 가이드는 SGLang의 Function calling 기능을 사용하는 방법을 보여줍니다.
현재 지원되는 파서 (Currently supported parsers):
| Parser | Supported Models | Notes |
|---|---|---|
apertus2509 |
Apertus 2509 (예: swiss-ai/Apertus-{8,70}B-Instruct-2509) |
툴 호출이 단일 키 객체의 JSON 리스트로 방출됨: `< |
deepseekv3 |
DeepSeek-v3 (예: deepseek-ai/DeepSeek-V3-0324) |
시작 명령에 --chat-template ./examples/chat_template/tool_chat_template_deepseekv3.jinja 추가 권장. |
deepseekv31 |
DeepSeek-V3.1 및 DeepSeek-V3.2-Exp (예: deepseek-ai/DeepSeek-V3.1, deepseek-ai/DeepSeek-V3.2-Exp) |
시작 명령에 --chat-template ./examples/chat_template/tool_chat_template_deepseekv31.jinja(DeepSeek-V3.2는 ..deepseekv32.jinja) 추가 권장. |
deepseekv32 |
DeepSeek-V3.2 (deepseek-ai/DeepSeek-V3.2) |
|
glm |
GLM 시리즈 (예: zai-org/GLM-4.6) |
|
gpt-oss |
GPT-OSS (예: openai/gpt-oss-120b, openai/gpt-oss-20b, lmsys/gpt-oss-120b-bf16, lmsys/gpt-oss-20b-bf16) |
gpt-oss 툴 파서는 analysis 채널 이벤트를 걸러내고 정상 텍스트만 보존함. 설명이 analysis 채널에 있으면 콘텐츠가 비어 있을 수 있음. 우회하려면 툴 결과를 role="tool" 메시지로 반환해 툴 라운드를 완료하면 모델이 최종 콘텐츠를 생성할 수 있음. |
kimi_k2 |
moonshotai/Kimi-K2-Instruct |
|
llama3 |
Llama 3.1 / 3.2 / 3.3 (예: meta-llama/Llama-3.1-8B-Instruct, meta-llama/Llama-3.2-1B-Instruct, meta-llama/Llama-3.3-70B-Instruct) |
|
llama4 |
Llama 4 (예: meta-llama/Llama-4-Scout-17B-16E-Instruct) |
|
mistral |
Mistral (예: mistralai/Mistral-7B-Instruct-v0.3, mistralai/Mistral-Nemo-Instruct-2407, mistralai/Mistral-7B-v0.3) |
|
pythonic |
Llama-3.2 / Llama-3.3 / Llama-4 | 모델이 함수 호출을 Python 코드로 출력함. --tool-call-parser pythonic 필요하며 특정 채팅 템플릿과 함께 사용 권장. |
qwen |
Qwen 시리즈 (예: Qwen/Qwen3-Next-80B-A3B-Instruct, Qwen/Qwen3-VL-30B-A3B-Thinking), Qwen3-Coder 제외 |
|
qwen3_coder |
Qwen3-Coder (예: Qwen/Qwen3-Coder-30B-A3B-Instruct) |
|
step3 |
Step-3 |
OpenAI 호환 API (OpenAI Compatible API)
서버 시작 (Launching the Server)
import json
from sglang.test.doc_patch import launch_server_cmd
from sglang.utils import wait_for_server, print_highlight, terminate_process
from openai import OpenAI
server_process, port = launch_server_cmd(
"python3 -m sglang.launch_server --model-path Qwen/Qwen2.5-7B-Instruct --tool-call-parser qwen25 --host 0.0.0.0 --log-level warning" # qwen25
)
wait_for_server(f"http://localhost:{port}")
--tool-call-parser가 응답 해석에 사용되는 파서를 정의한다는 점을 기억하세요.
함수 호출용 도구 정의 (Define Tools for Function Call)
아래는 도구를 딕셔너리로 정의하는 Python 스니펫입니다. 딕셔너리는 도구 이름, 설명, 정의된 파라미터(property defined Parameters)를 포함합니다.
# Define tools
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city to find the weather for, e.g. 'San Francisco'",
},
"state": {
"type": "string",
"description": "the two-letter abbreviation for the state that the city is"
" in, e.g. 'CA' which would mean 'California'",
},
"unit": {
"type": "string",
"description": "The unit to fetch the temperature in",
"enum": ["celsius", "fahrenheit"],
},
},
"required": ["city", "state", "unit"],
},
},
}
]
메시지 정의 (Define Messages)
def get_messages():
return [
{
"role": "user",
"content": "What's the weather like in Boston today? Output a reasoning before act, then use the tools to help you.",
}
]
messages = get_messages()
클라이언트 초기화 (Initialize the Client)
# Initialize OpenAI-like client
client = OpenAI(api_key="None", base_url=f"http://0.0.0.0:{port}/v1")
model_name = client.models.list().data[0].id
비스트리밍 요청 (Non-Streaming Request)
# Non-streaming mode test
response_non_stream = client.chat.completions.create(
model=model_name,
messages=messages,
temperature=0,
top_p=0.95,
max_tokens=1024,
stream=False, # Non-streaming
tools=tools,
)
print_highlight("Non-stream response:")
print_highlight(response_non_stream)
print_highlight("==== content ====")
print_highlight(response_non_stream.choices[0].message.content)
print_highlight("==== tool_calls ====")
print_highlight(response_non_stream.choices[0].message.tool_calls)
도구 처리 (Handle Tools)
엔진이 특정 도구를 호출해야 한다고 판단하면 응답을 통해 인자 또는 부분 인자를 반환합니다. 이 인자를 파싱한 뒤 해당 도구를 호출할 수 있습니다.
name_non_stream = response_non_stream.choices[0].message.tool_calls[0].function.name
arguments_non_stream = (
response_non_stream.choices[0].message.tool_calls[0].function.arguments
)
print_highlight(f"Final streamed function call name: {name_non_stream}")
print_highlight(f"Final streamed function call arguments: {arguments_non_stream}")
스트리밍 요청 (Streaming Request)
# Streaming mode test
print_highlight("Streaming response:")
response_stream = client.chat.completions.create(
model=model_name,
messages=messages,
temperature=0,
top_p=0.95,
max_tokens=1024,
stream=True, # Enable streaming
tools=tools,
)
texts = ""
tool_calls = []
name = ""
arguments = ""
for chunk in response_stream:
if chunk.choices[0].delta.content:
texts += chunk.choices[0].delta.content
if chunk.choices[0].delta.tool_calls:
tool_calls.append(chunk.choices[0].delta.tool_calls[0])
print_highlight("==== Text ====")
print_highlight(texts)
print_highlight("==== Tool Call ====")
for tool_call in tool_calls:
print_highlight(tool_call)
도구 처리 (Handle Tools)
엔진이 특정 도구를 호출해야 한다고 판단하면 응답을 통해 인자 또는 부분 인자를 반환합니다. 이 인자를 파싱한 뒤 해당 도구를 호출할 수 있습니다.
# Parse and combine function call arguments
arguments = []
for tool_call in tool_calls:
if tool_call.function.name:
print_highlight(f"Streamed function call name: {tool_call.function.name}")
if tool_call.function.arguments:
arguments.append(tool_call.function.arguments)
# Combine all fragments into a single JSON string
full_arguments = "".join(arguments)
print_highlight(f"streamed function call arguments: {full_arguments}")
도구 함수 정의 (Define a Tool Function)
# This is a demonstration, define real function according to your usage.
def get_current_weather(city: str, state: str, unit: "str"):
return (
f"The weather in {city}, {state} is 85 degrees {unit}. It is "
"partly cloudly, with highs in the 90's."
)
available_tools = {"get_current_weather": get_current_weather}
도구 실행 (Execute the Tool)
messages.append(response_non_stream.choices[0].message)
# Call the corresponding tool function
tool_call = messages[-1].tool_calls[0]
tool_name = tool_call.function.name
tool_to_call = available_tools[tool_name]
result = tool_to_call(**(json.loads(tool_call.function.arguments)))
print_highlight(f"Function call result: {result}")
# messages.append({"role": "tool", "content": result, "name": tool_name})
messages.append(
{
"role": "tool",
"tool_call_id": tool_call.id,
"content": str(result),
"name": tool_name,
}
)
print_highlight(f"Updated message history: {messages}")
결과를 모델로 다시 보내기 (Send Results Back to Model)
final_response = client.chat.completions.create(
model=model_name,
messages=messages,
temperature=0,
top_p=0.95,
stream=False,
tools=tools,
)
print_highlight("Non-stream response:")
print_highlight(final_response)
print_highlight("==== Text ====")
print_highlight(final_response.choices[0].message.content)
네이티브 API와 SGLang 런타임 (Native API and SGLang Runtime, SRT)
from transformers import AutoTokenizer
import requests
# generate an answer
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen2.5-7B-Instruct")
messages = get_messages()
input = tokenizer.apply_chat_template(
messages, tokenize=False, add_generation_prompt=True, tools=tools, return_dict=False
)
gen_url = f"http://localhost:{port}/generate"
gen_data = {
"text": input,
"sampling_params": {
"skip_special_tokens": False,
"max_new_tokens": 1024,
"temperature": 0,
"top_p": 0.95,
},
}
gen_response = requests.post(gen_url, json=gen_data).json()["text"]
print_highlight("==== Response ====")
print_highlight(gen_response)
# parse the response
parse_url = f"http://localhost:{port}/parse_function_call"
function_call_input = {
"text": gen_response,
"tool_call_parser": "qwen25",
"tools": tools,
}
function_call_response = requests.post(parse_url, json=function_call_input)
function_call_response_json = function_call_response.json()
print_highlight("==== Text ====")
print(function_call_response_json["normal_text"])
print_highlight("==== Calls ====")
print("function name: ", function_call_response_json["calls"][0]["name"])
print("function arguments: ", function_call_response_json["calls"][0]["parameters"])
terminate_process(server_process)
오프라인 엔진 API (Offline Engine API)
import sglang as sgl
from sglang.srt.function_call.function_call_parser import FunctionCallParser
from sglang.srt.managers.io_struct import Tool, Function
llm = sgl.Engine(model_path="Qwen/Qwen2.5-7B-Instruct")
tokenizer = llm.tokenizer_manager.tokenizer
input_ids = tokenizer.apply_chat_template(
messages, tokenize=True, add_generation_prompt=True, tools=tools, return_dict=False
)
# Note that for gpt-oss tool parser, adding "no_stop_trim": True
# to make sure the tool call token <call> is not trimmed.
sampling_params = {
"max_new_tokens": 1024,
"temperature": 0,
"top_p": 0.95,
"skip_special_tokens": False,
}
# 1) Offline generation
result = llm.generate(input_ids=input_ids, sampling_params=sampling_params)
generated_text = result["text"] # Assume there is only one prompt
print_highlight("=== Offline Engine Output Text ===")
print_highlight(generated_text)
# 2) Parse using FunctionCallParser
def convert_dict_to_tool(tool_dict: dict) -> Tool:
function_dict = tool_dict.get("function", {})
return Tool(
type=tool_dict.get("type", "function"),
function=Function(
name=function_dict.get("name"),
description=function_dict.get("description"),
parameters=function_dict.get("parameters"),
),
)
tools = [convert_dict_to_tool(raw_tool) for raw_tool in tools]
parser = FunctionCallParser(tools=tools, tool_call_parser="qwen25")
normal_text, calls = parser.parse_non_stream(generated_text)
print_highlight("=== Parsing Result ===")
print("Normal text portion:", normal_text)
print_highlight("Function call portion:")
for call in calls:
# call: ToolCallItem
print_highlight(f" - tool name: {call.name}")
print_highlight(f" parameters: {call.parameters}")
# 3) If needed, perform additional logic on the parsed functions, such as automatically calling the corresponding function to obtain a return value, etc.
llm.shutdown()
툴 선택 모드 (Tool Choice Mode)
SGLang은 모델이 언제 어떤 도구를 호출해야 하는지 제어하는 OpenAI의 tool_choice 파라미터를 지원합니다. 이 기능은 신뢰할 수 있는 도구 호출 동작을 보장하기 위해 EBNF(Extended Backus-Naur Form) 문법으로 구현됩니다.
지원되는 툴 선택 옵션 (Supported Tool Choice Options)
tool_choice="required": 모델이 최소한 하나의 도구를 호출하도록 강제tool_choice={"type": "function", "function": {"name": "specific_function"}}: 모델이 특정 함수를 호출하도록 강제
백엔드 호환성 (Backend Compatibility)
툴 선택은 기본 문법 백엔드인 Xgrammar 백엔드(--grammar-backend xgrammar)에서 완전히 지원됩니다. 그러나 outlines 같은 다른 백엔드에서는 완전히 지원되지 않을 수 있습니다.
예시: 필수 툴 선택 (Example: Required Tool Choice)
from openai import OpenAI
from sglang.utils import wait_for_server, print_highlight, terminate_process
from sglang.test.doc_patch import launch_server_cmd
# Start a new server session for tool choice examples
server_process_tool_choice, port_tool_choice = launch_server_cmd(
"python3 -m sglang.launch_server --model-path Qwen/Qwen2.5-7B-Instruct --tool-call-parser qwen25 --host 0.0.0.0 --log-level warning"
)
wait_for_server(f"http://localhost:{port_tool_choice}")
# Initialize client for tool choice examples
client_tool_choice = OpenAI(
api_key="None", base_url=f"http://0.0.0.0:{port_tool_choice}/v1"
)
model_name_tool_choice = client_tool_choice.models.list().data[0].id
# Example with tool_choice="required" - forces the model to call a tool
messages_required = [
{"role": "user", "content": "Hello, what is the capital of France?"}
]
# Define tools
tools = [
{
"type": "function",
"function": {
"name": "get_current_weather",
"description": "Get the current weather in a given location",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The city to find the weather for, e.g. 'San Francisco'",
},
"unit": {
"type": "string",
"description": "The unit to fetch the temperature in",
"enum": ["celsius", "fahrenheit"],
},
},
"required": ["city", "unit"],
},
},
}
]
response_required = client_tool_choice.chat.completions.create(
model=model_name_tool_choice,
messages=messages_required,
temperature=0,
max_tokens=1024,
tools=tools,
tool_choice="required", # Force the model to call a tool
)
print_highlight("Response with tool_choice='required':")
print("Content:", response_required.choices[0].message.content)
print("Tool calls:", response_required.choices[0].message.tool_calls)
예시: 특정 함수 선택 (Example: Specific Function Choice)
# Example with specific function choice - forces the model to call a specific function
messages_specific = [
{"role": "user", "content": "What are the most attactive places in France?"}
]
response_specific = client_tool_choice.chat.completions.create(
model=model_name_tool_choice,
messages=messages_specific,
temperature=0,
max_tokens=1024,
tools=tools,
tool_choice={
"type": "function",
"function": {"name": "get_current_weather"},
}, # Force the model to call the specific get_current_weather function
)
print_highlight("Response with specific function choice:")
print("Content:", response_specific.choices[0].message.content)
print("Tool calls:", response_specific.choices[0].message.tool_calls)
if response_specific.choices[0].message.tool_calls:
tool_call = response_specific.choices[0].message.tool_calls[0]
print_highlight(f"Called function: {tool_call.function.name}")
print_highlight(f"Arguments: {tool_call.function.arguments}")
terminate_process(server_process_tool_choice)
Pythonic 툴 호출 형식 (Llama-3.2 / Llama-3.3 / Llama-4)
일부 Llama 모델(Llama-3.2-1B, Llama-3.2-3B, Llama-3.3-70B, Llama-4 등)은 "pythonic" 툴 호출 형식을 지원하며, 모델이 함수 호출을 Python 코드로 출력합니다. 예:
[get_current_weather(city="San Francisco", state="CA", unit="celsius")]
- 출력은 함수 호출의 Python 리스트이며, 인자는 Python 리터럴(JSON 아님)입니다.
- 같은 리스트에 여러 툴 호출을 반환할 수 있습니다:
[get_current_weather(city="San Francisco", state="CA", unit="celsius"),
get_current_weather(city="New York", state="NY", unit="fahrenheit")]
자세한 내용은 Meta의 Zero shot function calling 문서를 참고하세요.
이 기능은 Blackwell에서 아직 개발 중입니다.
활성화 방법 (How to enable)
--tool-call-parser pythonic으로 서버 시작- 모델용 개선된 템플릿으로
--chat-template을 지정할 수도 있습니다 (예:--chat-template=examples/chat_template/tool_chat_template_llama4_pythonic.jinja). 이는 모델이 유효한 pythonic 툴 호출 출력을 안정적으로 생성하려면 특수 프롬프트 형식을 기대하기 때문에 권장됩니다. 템플릿은 프롬프트 구조(특수 토큰,<|eom|>같은 메시지 경계, 함수 호출 구분자)가 모델이 훈련·파인튜닝된 것과 일치하도록 보장합니다. 올바른 채팅 템플릿을 사용하지 않으면 툴 호출이 실패하거나 일관되지 않은 결과를 만들 수 있습니다.
채팅 템플릿 없이 pythonic 툴 호출 출력 강제하기
채팅 템플릿을 지정하고 싶지 않다면, pythonic 출력을 강제하기 위해 메시지에서 모델에 매우 명시적인 지시를 줘야 합니다. 예를 들어 Llama-3.2-1B-Instruct의 경우:
import openai
server_process, port = launch_server_cmd(
" python3 -m sglang.launch_server --model-path meta-llama/Llama-3.2-1B-Instruct --tool-call-parser pythonic --tp 1 --log-level warning" # llama-3.2-1b-instruct
)
wait_for_server(f"http://localhost:{port}")
tools = [
{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get the current weather for a given location.",
"parameters": {
"type": "object",
"properties": {
"location": {
"type": "string",
"description": "The name of the city or location.",
}
},
"required": ["location"],
},
},
},
{
"type": "function",
"function": {
"name": "get_tourist_attractions",
"description": "Get a list of top tourist attractions for a given city.",
"parameters": {
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "The name of the city to find attractions for.",
}
},
"required": ["city"],
},
},
},
]
def get_messages():
return [
{
"role": "system",
"content": (
"You are a travel assistant. "
"When asked to call functions, ALWAYS respond ONLY with a python list of function calls, "
"using this format: [func_name1(param1=value1, param2=value2), func_name2(param=value)]. "
"Do NOT use JSON, do NOT use variables, do NOT use any other format. "
"Here is an example:\n"
'[get_weather(location="Paris"), get_tourist_attractions(city="Paris")]'
),
},
{
"role": "user",
"content": (
"I'm planning a trip to Tokyo next week. What's the weather like and what are some top tourist attractions? "
"Propose parallel tool calls at once, using the python list of function calls format as shown above."
),
},
]
messages = get_messages()
client = openai.Client(base_url=f"http://localhost:{port}/v1", api_key="xxxxxx")
model_name = client.models.list().data[0].id
response_non_stream = client.chat.completions.create(
model=model_name,
messages=messages,
temperature=0,
top_p=0.9,
stream=False, # Non-streaming
tools=tools,
)
print_highlight("Non-stream response:")
print_highlight(response_non_stream)
response_stream = client.chat.completions.create(
model=model_name,
messages=messages,
temperature=0,
top_p=0.9,
stream=True,
tools=tools,
)
texts = ""
tool_calls = []
name = ""
arguments = ""
for chunk in response_stream:
if chunk.choices[0].delta.content:
texts += chunk.choices[0].delta.content
if chunk.choices[0].delta.tool_calls:
tool_calls.append(chunk.choices[0].delta.tool_calls[0])
print_highlight("Streaming Response:")
print_highlight("==== Text ====")
print_highlight(texts)
print_highlight("==== Tool Call ====")
for tool_call in tool_calls:
print_highlight(tool_call)
terminate_process(server_process)
참고: 모델이 그 형식으로 많이 파인튜닝되었다면 여전히 JSON으로 기본 설정될 수 있습니다. 채팅 템플릿을 사용하지 않으면 pythonic 출력 확률을 높이는 유일한 방법은 프롬프트 엔지니어링(예제 포함)입니다.
새 모델을 지원하는 방법 (How to support a new model?)
- 모델의 툴 태그로 sglang/srt/function_call_parser.py의 TOOLS_TAG_LIST를 업데이트. 현재 지원되는 태그는:
TOOLS_TAG_LIST = [
“<|plugin|>“,
“<function=“,
“<tool_call>“,
“<|python_tag|>“,
“[TOOL_CALLS]”
]
- sglang/srt/function_call_parser.py에서
BaseFormatDetector를 상속하는 새 디텍터 클래스 생성. 디텍터는 모델의 특정 함수 호출 형식을 처리해야 합니다. 예:
class NewModelDetector(BaseFormatDetector):
- 모든 형식 디텍터를 관리하는
MultiFormatParser클래스에 새 디텍터 추가.