Entropic 2.3.8
Local-first agentic inference engine
Loading...
Searching...
No Matches
qwen35_adapter.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
12#include "qwen35_adapter.h"
13
15
16#include <nlohmann/json.hpp>
17
18#include <regex>
19#include <sstream>
20
21namespace entropic {
22
23namespace {
24auto logger = entropic::log::get("inference.adapter.qwen35");
25
27constexpr const char* TOOL_RESULT_SUFFIX =
28 "Continue. Batch multiple tool calls in one response when possible.";
29
36std::string generate_uuid() {
37 static std::atomic<uint64_t> counter{0};
38 return "tc-" + std::to_string(counter.fetch_add(1, std::memory_order_relaxed));
39}
40
41} // anonymous namespace
42
43// ── Tool call parsing ──────────────────────────────────────
44
55ParseResult Qwen35Adapter::parse_tool_calls(const std::string& content) const {
56 ParseResult result;
57
58 // Primary: XML function calls
59 auto calls = parse_xml_function_calls(content);
60
61 // Fallback: tagged JSON (base class)
62 if (calls.empty()) {
63 calls = parse_tagged_tool_calls(content);
64 }
65
66 result.tool_calls = std::move(calls);
67 result.cleaned_content = clean_content(content);
68 return result;
69}
70
81std::vector<ToolCall> Qwen35Adapter::parse_xml_function_calls(
82 const std::string& content) const
83{
84 std::vector<ToolCall> calls;
85 std::regex func_pattern(R"(<function=([^>]+)>([\s\S]*?)</function>)");
86
87 auto begin = std::sregex_iterator(content.begin(), content.end(), func_pattern);
88 auto end = std::sregex_iterator();
89
90 for (auto it = begin; it != end; ++it) {
91 std::string func_name = (*it)[1].str();
92 std::string func_body = (*it)[2].str();
93
94 // Trim whitespace from function name
95 auto ns = func_name.find_first_not_of(" \t");
96 auto ne = func_name.find_last_not_of(" \t");
97 if (ns != std::string::npos) {
98 func_name = func_name.substr(ns, ne - ns + 1);
99 }
100
101 auto arguments = extract_xml_parameters(func_body);
102
103 ToolCall tc;
104 tc.id = generate_uuid();
105 tc.name = func_name;
106 tc.arguments = std::move(arguments);
107 calls.push_back(std::move(tc));
108
109 logger->info("Parsed XML tool call: {}", func_name);
110 }
111 return calls;
112}
113
124std::unordered_map<std::string, std::string> Qwen35Adapter::extract_xml_parameters(
125 const std::string& func_body) const
126{
127 std::string body = func_body;
128
129 // Truncate at nested function start
130 auto nested = body.find("<function=");
131 if (nested != std::string::npos) {
132 logger->warn("Truncating function body at nested <function= tag");
133 body = body.substr(0, nested);
134 }
135
136 std::unordered_map<std::string, std::string> arguments;
137 std::regex param_pattern(R"(<parameter=([^>]+)>([\s\S]*?)</parameter>)");
138
139 auto begin = std::sregex_iterator(body.begin(), body.end(), param_pattern);
140 auto end = std::sregex_iterator();
141
142 for (auto it = begin; it != end; ++it) {
143 std::string key = (*it)[1].str();
144 std::string value = (*it)[2].str();
145
146 // Trim whitespace including newlines
147 auto ks = key.find_first_not_of(" \t\n\r");
148 auto ke = key.find_last_not_of(" \t\n\r");
149 if (ks != std::string::npos) key = key.substr(ks, ke - ks + 1);
150
151 auto vs = value.find_first_not_of(" \t\n\r");
152 auto ve = value.find_last_not_of(" \t\n\r");
153 if (vs != std::string::npos) value = value.substr(vs, ve - vs + 1);
154
155 if (key.empty() || value.empty()) {
156 logger->warn("Skipping empty XML parameter: key='{}' value='{}'", key, value);
157 continue;
158 }
159 arguments[key] = value;
160 }
161 return arguments;
162}
163
164// ── Tool result formatting ─────────────────────────────────
165
175 const ToolCall& tool_call,
176 const std::string& result) const
177{
178 Message msg;
179 msg.role = "user";
180 msg.content = "<tool_response>\n" + result +
181 "\n</tool_response>\n\n" + TOOL_RESULT_SUFFIX;
182 return msg;
183}
184
185// ── Tool definition formatting ─────────────────────────────
186
195 const std::vector<std::string>& tool_jsons) const
196{
197 nlohmann::json tool_defs = nlohmann::json::array();
198
199 for (const auto& json_str : tool_jsons) {
200 try {
201 auto j = nlohmann::json::parse(json_str);
202 tool_defs.push_back({
203 {"type", "function"},
204 {"function", {
205 {"name", j.value("name", "unknown")},
206 {"description", j.value("description", "")},
207 {"parameters", j.value("inputSchema",
208 nlohmann::json::object())}
209 }}
210 });
211 } catch (...) {
212 continue;
213 }
214 }
215
216 std::ostringstream out;
217 out << "# Tools\n\n"
218 << "You may call one or more functions to assist with the user query.\n"
219 << "Put your final answer OUTSIDE of any tool calls.\n\n"
220 << "Here are the available tools:\n"
221 << "<tools>\n"
222 << tool_defs.dump(2) << "\n"
223 << "</tools>\n\n"
224 << "For each function call, return within <tool_call></tool_call> XML tags:\n"
225 << "<tool_call>\n"
226 << "<function=example_function>\n"
227 << "<parameter=param_name>value</parameter>\n"
228 << "</function>\n"
229 << "</tool_call>";
230 return out.str();
231}
232
233// ── Content cleaning ───────────────────────────────────────
234
242std::string Qwen35Adapter::clean_content(const std::string& content) const {
243 // Remove <tool_call>...</tool_call> blocks
244 std::string cleaned = std::regex_replace(content,
245 std::regex(R"(<tool_call>\s*[\s\S]*?\s*</tool_call>)"), "");
246
247 // Remove standalone <function=...>...</function>
248 cleaned = std::regex_replace(cleaned,
249 std::regex(R"(<function=[^>]+>[\s\S]*?</function>)"), "");
250
251 // Strip think blocks
252 cleaned = strip_think_blocks(cleaned);
253 return cleaned;
254}
255
256// ── Vision / multimodal (v1.9.11) ──────────────────────────
257
259static constexpr const char* VISION_INSTRUCTION =
260 "\n\nYou can see and analyze images. When the user shares an "
261 "image, describe what you observe before responding to their "
262 "question.";
263
273 const std::string& base_system,
274 bool has_vision) const {
275 if (!has_vision) {
276 return base_system;
277 }
278 return base_system + VISION_INSTRUCTION;
279}
280
289 const std::vector<ContentPart>& parts) const {
290 // Qwen3.5 uses OpenAI content array format natively —
291 // delegate to base class default.
293}
294
295} // namespace entropic
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 in <tools> tags with OpenAI function JSON.
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, fallback to tagged JSON.
std::string format_system_with_vision(const std::string &base_system, bool has_vision) const override
Qwen3.5 vision system prompt extension.
std::string format_content_parts(const std::vector< ContentPart > &parts) const override
Qwen3.5 content part formatting.
spdlog initialization and logger access.
ENTROPIC_EXPORT std::shared_ptr< spdlog::logger > get(const std::string &name)
Get or create a named logger.
Definition logging.cpp:211
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.
Definition backend.cpp:840
Qwen 3.5 chat adapter.
A message in a conversation.
Definition message.h:35
std::string content
Message text content (always populated)
Definition message.h:37
std::string role
Message role.
Definition message.h:36
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.
A tool call request parsed from model output.
Definition tool_call.h:31