Entropic 2.9.5
Local-first agentic inference engine
Loading...
Searching...
No Matches
qwen36_adapter.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
15#include "qwen36_adapter.h"
16
18
20
21#include <nlohmann/json.hpp>
22
23#include <regex>
24#include <sstream>
25
26namespace entropic {
27
28namespace {
29auto logger = entropic::log::get("inference.adapter.qwen36");
30
32constexpr const char* TOOL_RESULT_SUFFIX =
33 "Continue. Batch multiple tool calls in one response when possible.";
34
41std::string generate_uuid() {
42 static std::atomic<uint64_t> counter{0};
43 return "tc-" + std::to_string(counter.fetch_add(1, std::memory_order_relaxed));
44}
45
47constexpr const char* VISION_INSTRUCTION =
48 "\n\nYou can see and analyze images. When the user shares an "
49 "image, describe what you observe before responding to their "
50 "question.";
51
52} // anonymous namespace
53
54// ── Tool call parsing ──────────────────────────────────────
55
68ParseResult Qwen36Adapter::parse_tool_calls(const std::string& content) const {
69 ParseResult result;
70
71 auto calls = parse_xml_function_calls(content);
72 if (calls.empty()) {
73 calls = parse_tagged_tool_calls(content);
74 }
75
76 result.tool_calls = std::move(calls);
77 result.cleaned_content = clean_content(content);
78 return result;
79}
80
91std::vector<ToolCall> Qwen36Adapter::parse_xml_function_calls(
92 const std::string& content) const
93{
94 std::vector<ToolCall> calls;
95 std::regex func_pattern(R"(<function=([^>]+)>([\s\S]*?)</function>)");
96
97 auto begin = std::sregex_iterator(content.begin(), content.end(), func_pattern);
98 auto end = std::sregex_iterator();
99
100 for (auto it = begin; it != end; ++it) {
101 std::string func_name = (*it)[1].str();
102 std::string func_body = (*it)[2].str();
103
104 auto ns = func_name.find_first_not_of(" \t");
105 auto ne = func_name.find_last_not_of(" \t");
106 if (ns != std::string::npos) {
107 func_name = func_name.substr(ns, ne - ns + 1);
108 }
109
110 ToolCall tc;
111 tc.id = generate_uuid();
112 tc.name = func_name;
113 tc.arguments = extract_xml_parameters(func_body);
114 calls.push_back(std::move(tc));
115
116 logger->info("Parsed XML tool call: {}", func_name);
117 }
118 return calls;
119}
120
132std::unordered_map<std::string, std::string> Qwen36Adapter::extract_xml_parameters(
133 const std::string& func_body) const
134{
136 func_body, logger);
137}
138
139// ── Tool result formatting ─────────────────────────────────
140
150 const ToolCall& /*tool_call*/,
151 const std::string& result) const
152{
153 Message msg;
154 msg.role = "user";
155 msg.content = "<tool_response>\n" + result +
156 "\n</tool_response>\n\n" + TOOL_RESULT_SUFFIX;
157 return msg;
158}
159
160// ── Tool definition formatting ─────────────────────────────
161
175 const std::vector<std::string>& tool_jsons) const
176{
177 nlohmann::json tool_defs = nlohmann::json::array();
178 for (const auto& json_str : tool_jsons) {
179 try {
180 auto j = nlohmann::json::parse(json_str);
181 tool_defs.push_back({
182 {"type", "function"},
183 {"function", {
184 {"name", j.value("name", "unknown")},
185 {"description", j.value("description", "")},
186 {"parameters", j.value("inputSchema",
187 nlohmann::json::object())}
188 }}
189 });
190 } catch (...) {
191 continue;
192 }
193 }
194
195 std::ostringstream out;
196 out << "# Tools\n\n"
197 << "You may call one or more functions to assist with the user query.\n"
198 << "Put your final answer OUTSIDE of any tool calls.\n\n"
199 << "Here are the available tools:\n"
200 << "<tools>\n"
201 << tool_defs.dump(2) << "\n"
202 << "</tools>\n\n"
203 << "For each function call, return within <tool_call></tool_call> XML tags:\n"
204 << "<tool_call>\n"
205 << "<function=example_function>\n"
206 << "<parameter=param_name>value</parameter>\n"
207 << "</function>\n"
208 << "</tool_call>";
209 return out.str();
210}
211
212// ── Content cleaning ───────────────────────────────────────
213
221std::string Qwen36Adapter::clean_content(const std::string& content) const {
222 std::string cleaned = std::regex_replace(content,
223 std::regex(R"(<tool_call>\s*[\s\S]*?\s*</tool_call>)"), "");
224 cleaned = std::regex_replace(cleaned,
225 std::regex(R"(<function=[^>]+>[\s\S]*?</function>)"), "");
226 cleaned = strip_think_blocks(cleaned);
227 return cleaned;
228}
229
230// ── Vision / multimodal ────────────────────────────────────
231
241 const std::string& base_system,
242 bool has_vision) const {
243 if (!has_vision) {
244 return base_system;
245 }
246 return base_system + VISION_INSTRUCTION;
247}
248
257 const std::vector<ContentPart>& parts) const {
259}
260
261} // 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 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.
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.6 chat adapter (v2.1.9, gh#45).
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
std::unordered_map< std::string, std::string > parse_xml_parameters(const std::string &func_body, const std::shared_ptr< spdlog::logger > &logger)
Extract <parameter=NAME>value</parameter> pairs from a function body.
Shared <parameter=NAME>value</parameter> extractor for XML-format chat adapters (Qwen 3....