Skip to content

Commit 0c87013

Browse files
author
ochafik
committed
tool-call: test/fix functionary-medium-v3.1's template (can "look" like llama3.1 template)
1 parent 8e4a9ba commit 0c87013

File tree

8 files changed

+116
-10
lines changed

8 files changed

+116
-10
lines changed

common/tool-call.cpp

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,16 @@ static llama_tool_calls parse_functionary_tool_calls(const std::string& input, c
191191
}
192192

193193
static llama_tool_calls parse_functionary_v3_llama_3_1_tool_calls(const std::string& input) {
194+
// This version of Functionary still supports the llama 3.1 tool call format for the python tool.
195+
static std::regex python_tag_regex(R"(<\|python_tag\|>([\s\S\n]*)$)");
196+
std::smatch match;
197+
if (std::regex_search(input, match, python_tag_regex)) {
198+
return {
199+
match.prefix().str(), {
200+
{"ipython", (json {{"code", match[1].str()}}).dump()},
201+
}
202+
};
203+
}
194204
static std::regex function_regex(R"(<function=(\w+)>)");
195205
static std::regex close_regex(R"(</function>)");
196206
return parse_functionary_tool_calls(input, function_regex, close_regex);
@@ -205,12 +215,12 @@ static llama_tool_calls parse_functionary_v3_tool_calls(const std::string& input
205215
llama_tool_calls parse_tool_calls(const json & tools, const std::string & chat_template, const std::string& input) {
206216
if (needs_hermes_pro_tool_call(chat_template)) {
207217
return parse_hermes_tool_calls(input);
208-
} else if (needs_llama_3_1_tool_call(chat_template)) {
209-
return parse_llama_3_1_tool_calls(tools, input);
210218
} else if (needs_functionary_v3_tool_call(chat_template)) {
211219
return parse_functionary_v3_tool_calls(input);
212220
} else if (needs_functionary_v3_llama_3_1_tool_call(chat_template)) {
213221
return parse_functionary_v3_llama_3_1_tool_calls(input);
222+
} else if (needs_llama_3_1_tool_call(chat_template)) {
223+
return parse_llama_3_1_tool_calls(tools, input);
214224
} else {
215225
throw std::runtime_error("Unsupported chat template for tool calls");
216226
}

examples/server/tests/features/tool_call.feature

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,29 +12,31 @@ Feature: llama.cpp server
1212
And 8192 KV cache size
1313
And 32 as batch size
1414
And 2 slots
15-
And 64 server max tokens to predict
1615
And prometheus compatible metrics exposed
1716
And jinja templates are enabled
1817

19-
@wip
18+
2019
Scenario Outline: OAI Compatibility w/ required tool
2120
Given a chat template file ../../../tests/chat/templates/<template_name>.jinja
2221
And the server is starting
2322
And the server is healthy
2423
And a model test
25-
And <n> max tokens to predict
24+
And <n_predict> max tokens to predict
2625
And a user prompt write a hello world in python
2726
And a tool choice <tool_choice>
2827
And tools <tools>
2928
And an OAI compatible chat completions request with no api error
3029
Then tool <tool_name> is called with arguments <tool_arguments>
3130

3231
Examples: Prompts
33-
| template_name | n | tool_name | tool_arguments | tool_choice | tools |
34-
| meta-llama-Meta-Llama-3.1-8B-Instruct | 64 | test | {} | required | [{"type":"function", "function": {"name": "test", "description": "", "parameters": {"type": "object", "properties": {}}}}] |
35-
| meta-llama-Meta-Llama-3.1-8B-Instruct | 16 | ipython | {"code": "it and "} | required | [{"type":"function", "function": {"name": "ipython", "description": "", "parameters": {"type": "object", "properties": {"code": {"type": "string", "description": ""}}, "required": ["code"]}}}] |
36-
| meetkai-functionary-medium-v3.2 | 64 | test | {} | required | [{"type":"function", "function": {"name": "test", "description": "", "parameters": {"type": "object", "properties": {}}}}] |
37-
| meetkai-functionary-medium-v3.2 | 64 | ipython | {"code": "Yes,"} | required | [{"type":"function", "function": {"name": "ipython", "description": "", "parameters": {"type": "object", "properties": {"code": {"type": "string", "description": ""}}, "required": ["code"]}}}] |
32+
| template_name | n_predict | tool_name | tool_arguments | tool_choice | tools |
33+
| meetkai-functionary-medium-v3.1 | 128 | test | {} | required | [{"type":"function", "function": {"name": "test", "description": "", "parameters": {"type": "object", "properties": {}}}}] |
34+
| meetkai-functionary-medium-v3.1 | 128 | ipython | {"code": "Yes, you can."} | required | [{"type":"function", "function": {"name": "ipython", "description": "", "parameters": {"type": "object", "properties": {"code": {"type": "string", "description": ""}}, "required": ["code"]}}}] |
35+
| meetkai-functionary-medium-v3.2 | 128 | test | {} | required | [{"type":"function", "function": {"name": "test", "description": "", "parameters": {"type": "object", "properties": {}}}}] |
36+
| meetkai-functionary-medium-v3.2 | 128 | ipython | {"code": "Yes,"} | required | [{"type":"function", "function": {"name": "ipython", "description": "", "parameters": {"type": "object", "properties": {"code": {"type": "string", "description": ""}}, "required": ["code"]}}}] |
37+
| meta-llama-Meta-Llama-3.1-8B-Instruct | 64 | test | {} | required | [{"type":"function", "function": {"name": "test", "description": "", "parameters": {"type": "object", "properties": {}}}}] |
38+
| meta-llama-Meta-Llama-3.1-8B-Instruct | 16 | ipython | {"code": "it and "} | required | [{"type":"function", "function": {"name": "ipython", "description": "", "parameters": {"type": "object", "properties": {"code": {"type": "string", "description": ""}}, "required": ["code"]}}}] |
39+
3840

3941
Scenario: OAI Compatibility w/ no tool
4042
Given a chat template file ../../../tests/chat/templates/meta-llama-Meta-Llama-3.1-8B-Instruct.jinja
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<|startoftext|><|start_header_id|>system<|end_header_id|>
2+
3+
4+
Cutting Knowledge Date: December 2023
5+
6+
<|eot_id|><|start_header_id|>user<|end_header_id|>
7+
8+
What's your favourite LLM framework?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
9+
10+
llama.cpp!<|eot_id|><|start_header_id|>assistant<|end_header_id|>
11+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<|startoftext|><|start_header_id|>system<|end_header_id|>
2+
3+
4+
Cutting Knowledge Date: December 2023
5+
6+
<|eot_id|><|start_header_id|>system<|end_header_id|>
7+
8+
You only tell the truth.<|eot_id|><|start_header_id|>user<|end_header_id|>
9+
10+
What's your favourite LLM framework?<|eot_id|><|start_header_id|>assistant<|end_header_id|>
11+
12+
llama.cpp!<|eot_id|><|start_header_id|>assistant<|end_header_id|>
13+
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ERROR: can only concatenate str (not "dict") to str
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{# version=v3-llama3.1 #}{%- if not tools is defined -%}
2+
{%- set tools = none -%}
3+
{%- endif -%}
4+
5+
{%- set has_code_interpreter = tools | selectattr("type", "equalto", "code_interpreter") | list | length > 0 -%}
6+
{%- if has_code_interpreter -%}
7+
{%- set tools = tools | rejectattr("type", "equalto", "code_interpreter") | list -%}
8+
{%- endif -%}
9+
10+
{#- System message + builtin tools #}
11+
{{- bos_token + "<|start_header_id|>system<|end_header_id|>\n\n" }}
12+
{%- if has_code_interpreter %}
13+
{{- "Environment: ipython\n\n" }}
14+
{%- else -%}
15+
{{ "\n"}}
16+
{%- endif %}
17+
{{- "Cutting Knowledge Date: December 2023\n\n" }}
18+
{%- if tools %}
19+
{{- "\nYou have access to the following functions:\n\n" }}
20+
{%- for t in tools %}
21+
{%- if "type" in t -%}
22+
{{ "Use the function '"|safe + t["function"]["name"] + "' to '"|safe + t["function"]["description"] + "'\n"|safe + t["function"] | tojson() }}
23+
{%- else -%}
24+
{{ "Use the function '"|safe + t["name"] + "' to '"|safe + t["description"] + "'\n"|safe + t | tojson() }}
25+
{%- endif -%}
26+
{{- "\n\n" }}
27+
{%- endfor %}
28+
{{- '\nThink very carefully before calling functions.\nIf a you choose to call a function ONLY reply in the following format:\n<{start_tag}={function_name}>{parameters}{end_tag}\nwhere\n\nstart_tag => `<function`\nparameters => a JSON dict with the function argument name as key and function argument value as value.\nend_tag => `</function>`\n\nHere is an example,\n<function=example_function_name>{"example_name": "example_value"}</function>\n\nReminder:\n- If looking for real time information use relevant functions before falling back to brave_search\n- Function calls MUST follow the specified format, start with <function= and end with </function>\n- Required parameters MUST be specified\n- Only call one function at a time\n- Put the entire function call reply on one line\n\n' -}}
29+
{%- endif %}
30+
{{- "<|eot_id|>" -}}
31+
32+
{%- for message in messages -%}
33+
{%- if message['role'] == 'user' or message['role'] == 'system' -%}
34+
{{ '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n' + message['content'] + '<|eot_id|>' }}
35+
{%- elif message['role'] == 'tool' -%}
36+
{{ '<|start_header_id|>ipython<|end_header_id|>\n\n' + message['content'] + '<|eot_id|>' }}
37+
{%- else -%}
38+
{{ '<|start_header_id|>' + message['role'] + '<|end_header_id|>\n\n'}}
39+
{%- if message['content'] -%}
40+
{{ message['content'] }}
41+
{%- endif -%}
42+
{%- if 'tool_calls' in message and message['tool_calls'] -%}
43+
{%- for tool_call in message['tool_calls'] -%}
44+
{%- if tool_call["function"]["name"] == "python" -%}
45+
{{ '<|python_tag|>' + tool_call['function']['arguments'] }}
46+
{%- else -%}
47+
{{ '<function=' + tool_call['function']['name'] + '>' + tool_call['function']['arguments'] + '</function>' }}
48+
{%- endif -%}
49+
{%- endfor -%}
50+
{{ '<|eom_id|>' }}
51+
{%- else -%}
52+
{{ '<|eot_id|>' }}
53+
{%- endif -%}
54+
{%- endif -%}
55+
{%- endfor -%}
56+
{%- if add_generation_prompt -%}
57+
{{ '<|start_header_id|>assistant<|end_header_id|>\n\n' }}
58+
{%- endif -%}

tests/test-tool-call.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,15 @@ int main() {
116116
}}
117117
},
118118
});
119+
test_parse_tool_call(tools, functionary_v3_llama_3_1_like_tmpl,
120+
"<function=test>{ } </function> ",
121+
" ",
122+
json {{
123+
{"function", {
124+
{"name", "test"},
125+
{"arguments", "{}"}
126+
}}
127+
}});
119128

120129
std::string llama_3_1_like_tmpl = "Llama 3.1 template should have <|start_header_id|> and <|python_tag|> inside it";
121130
test_parse_tool_call(tools, llama_3_1_like_tmpl,

tests/update_jinja_goldens.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,15 @@
2626
import re
2727
# import requests
2828

29+
logging.basicConfig(level=logging.INFO)
2930
logger = logging.getLogger(__name__)
3031

3132
model_ids = [
3233
"NousResearch/Hermes-3-Llama-3.1-70B",
3334
"NousResearch/Hermes-2-Pro-Llama-3-8B",
3435
"NousResearch/Hermes-2-Pro-Mistral-7B",
3536
"meetkai/functionary-medium-v3.2",
37+
"meetkai/functionary-medium-v3.1",
3638
"Qwen/Qwen2-7B-Instruct",
3739
"Qwen/Qwen2-VL-7B-Instruct",
3840
"Qwen/Qwen2.5-7B-Instruct",

0 commit comments

Comments
 (0)