Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion common/chat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1762,7 +1762,13 @@ static common_chat_params common_chat_params_init_lfm2_5(const common_chat_templ
)
);

auto content = p.content(p.until_one_of({"<|tool_call_start|>", "["}));
// A bare '[' in ordinary content (a list, an index, a type hint, etc.) is not a tool call;
// treating it as one would fail the parse and surface as an HTTP 500.
std::vector<std::string> content_stops = { "<|tool_call_start|>" };
foreach_function(inputs.tools, [&](const json & tool) {
content_stops.push_back("[" + tool.at("function").at("name").get<std::string>() + "(");
});
auto content = p.content(p.until_one_of(content_stops));
auto maybe_start = p.optional(p.literal("<|tool_call_start|>"));
return generation_prompt + reasoning + content + maybe_start + tool_calls + end;
});
Expand Down
23 changes: 23 additions & 0 deletions tests/test-chat.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4177,6 +4177,29 @@ static void test_template_output_peg_parsers(bool detailed_debug) {
))
.run();

// Bare-bracket content must not abort the request. LFM2.5 has no tool-call wrapper
// token; content must only stop at an actual "[{tool_name}(" prefix (matching the
// grammar triggers). A '[' that doesn't start a defined tool call (a list/index/
// type hint) used to fail the final parse and surface as an HTTP 500.
tst.test("Here is a Python list: [1, 2, 3] and that is all.")
.tools({ special_function_tool })
.expect_content("Here is a Python list: [1, 2, 3] and that is all.")
.run();

// The reported production shape: a code-mode reply that ends up as content
// containing a bracketed data structure.
tst.test("trades = [\n {\"timestamp\": \"09:30:00\", \"price\": 150.10, \"size\": 100}\n]")
.tools({ special_function_tool })
.expect_content("trades = [\n {\"timestamp\": \"09:30:00\", \"price\": 150.10, \"size\": 100}\n]")
.run();

// Streaming: a bare '[' mid-content must not retract previously streamed content.
tst.test("Here is a Python list: [1, 2")
.tools({ special_function_tool })
.is_partial(true)
.expect_content("Here is a Python list: [1, 2")
.run();

// Partial tool call (streaming)
tst.test("[special_function(arg1=")
.tools({ special_function_tool })
Expand Down