19#include <nlohmann/json.hpp>
30constexpr const char* TOOL_RESULT_SUFFIX =
31 "Continue. Batch multiple tool calls in one response when possible.";
40 static std::atomic<uint64_t> counter{0};
41 return "tc-" + std::to_string(counter.fetch_add(1, std::memory_order_relaxed));
46 "\n\nYou can see and analyze images. When the user shares an "
47 "image, describe what you observe before responding to their "
69 auto calls = parse_xml_function_calls(content);
89std::vector<ToolCall> Qwen36Adapter::parse_xml_function_calls(
90 const std::string& content)
const
92 std::vector<ToolCall> calls;
93 std::regex func_pattern(R
"(<function=([^>]+)>([\s\S]*?)</function>)");
95 auto begin = std::sregex_iterator(content.begin(), content.end(), func_pattern);
96 auto end = std::sregex_iterator();
98 for (
auto it = begin; it != end; ++it) {
99 std::string func_name = (*it)[1].str();
100 std::string func_body = (*it)[2].str();
102 auto ns = func_name.find_first_not_of(
" \t");
103 auto ne = func_name.find_last_not_of(
" \t");
104 if (ns != std::string::npos) {
105 func_name = func_name.substr(ns, ne - ns + 1);
111 tc.arguments = extract_xml_parameters(func_body);
112 calls.push_back(std::move(tc));
114 logger->info(
"Parsed XML tool call: {}", func_name);
129std::unordered_map<std::string, std::string> Qwen36Adapter::extract_xml_parameters(
130 const std::string& func_body)
const
132 std::string body = func_body;
133 auto nested = body.find(
"<function=");
134 if (nested != std::string::npos) {
135 logger->warn(
"Truncating function body at nested <function= tag");
136 body = body.substr(0, nested);
139 std::unordered_map<std::string, std::string> arguments;
140 std::regex param_pattern(R
"(<parameter=([^>]+)>([\s\S]*?)</parameter>)");
142 auto begin = std::sregex_iterator(body.begin(), body.end(), param_pattern);
143 auto end = std::sregex_iterator();
145 for (
auto it = begin; it != end; ++it) {
146 std::string key = (*it)[1].str();
147 std::string value = (*it)[2].str();
149 auto ks = key.find_first_not_of(
" \t\n\r");
150 auto ke = key.find_last_not_of(
" \t\n\r");
151 if (ks != std::string::npos) key = key.substr(ks, ke - ks + 1);
153 auto vs = value.find_first_not_of(
" \t\n\r");
154 auto ve = value.find_last_not_of(
" \t\n\r");
155 if (vs != std::string::npos) value = value.substr(vs, ve - vs + 1);
157 if (key.empty() || value.empty()) {
158 logger->warn(
"Skipping empty XML parameter: key='{}' value='{}'", key, value);
161 arguments[key] = value;
178 const std::string& result)
const
182 msg.
content =
"<tool_response>\n" + result +
183 "\n</tool_response>\n\n" + TOOL_RESULT_SUFFIX;
202 const std::vector<std::string>& tool_jsons)
const
204 nlohmann::json tool_defs = nlohmann::json::array();
205 for (
const auto& json_str : tool_jsons) {
207 auto j = nlohmann::json::parse(json_str);
208 tool_defs.push_back({
209 {
"type",
"function"},
211 {
"name", j.value(
"name",
"unknown")},
212 {
"description", j.value(
"description",
"")},
213 {
"parameters", j.value(
"inputSchema",
214 nlohmann::json::object())}
222 std::ostringstream out;
224 <<
"You may call one or more functions to assist with the user query.\n"
225 <<
"Put your final answer OUTSIDE of any tool calls.\n\n"
226 <<
"Here are the available tools:\n"
228 << tool_defs.dump(2) <<
"\n"
230 <<
"For each function call, return within <tool_call></tool_call> XML tags:\n"
232 <<
"<function=example_function>\n"
233 <<
"<parameter=param_name>value</parameter>\n"
248std::string Qwen36Adapter::clean_content(
const std::string& content)
const {
249 std::string cleaned = std::regex_replace(content,
250 std::regex(R
"(<tool_call>\s*[\s\S]*?\s*</tool_call>)"), "");
251 cleaned = std::regex_replace(cleaned,
252 std::regex(R
"(<function=[^>]+>[\s\S]*?</function>)"), "");
268 const std::string& base_system,
269 bool has_vision)
const {
284 const std::vector<ContentPart>& parts)
const {
virtual std::string format_content_parts(const std::vector< ContentPart > &parts) const
Convert multimodal content parts to adapter-specific format.
std::vector< ToolCall > parse_tagged_tool_calls(const std::string &content) const
Parse <tool_call>JSON</tool_call> tagged blocks.
std::string strip_think_blocks(const std::string &content) const
Strip all <think>...</think> blocks from content.
std::string format_tools(const std::vector< std::string > &tool_jsons) const override
Format tools as <tools>...</tools> with OpenAI function JSON.
std::string format_system_with_vision(const std::string &base_system, bool has_vision) const override
Append vision instructions to the system prompt when active.
std::string format_content_parts(const std::vector< ContentPart > &parts) const override
Format multimodal content parts (OpenAI-native).
Message format_tool_result(const ToolCall &tool_call, const std::string &result) const override
Wrap tool result in <tool_response> tags.
ParseResult parse_tool_calls(const std::string &content) const override
Parse XML function calls; fall back to tagged JSON.
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Activate model on GPU (WARM → ACTIVE).
static constexpr const char * VISION_INSTRUCTION
Vision instruction appended to system prompt.
std::string generate_uuid()
Generate a UUID v4 string.
Qwen 3.6 chat adapter (v2.1.9, gh#45).
A message in a conversation.
std::string content
Message text content (always populated)
std::string role
Message role.
Parsed tool call result: cleaned content + extracted calls.
std::string cleaned_content
Content with tool calls removed.
std::vector< ToolCall > tool_calls
Extracted tool calls.