Entropic 2.9.4
Local-first agentic inference engine
Loading...
Searching...
No Matches
nemotron3_adapter.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
15#include "nemotron3_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.nemotron3");
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
46} // anonymous namespace
47
48// ── Tool call parsing ──────────────────────────────────────
49
62ParseResult Nemotron3Adapter::parse_tool_calls(const std::string& content) const {
63 ParseResult result;
64
65 auto calls = parse_dsml_function_calls(content);
66 if (calls.empty()) {
67 calls = parse_xml_function_calls(content);
68 }
69 if (calls.empty()) {
70 calls = parse_tagged_tool_calls(content);
71 }
72
73 result.tool_calls = std::move(calls);
74 result.cleaned_content = clean_content(content);
75 return result;
76}
77
78// ── DSML invoke parsing (gh#70, native Nemotron 3 format) ──
79
92std::vector<ToolCall> Nemotron3Adapter::parse_dsml_function_calls(
93 const std::string& content) const
94{
95 std::vector<ToolCall> calls;
96 // Custom `re` delimiter: the pattern contains `)"` sequences (from
97 // the `name="..."` capture) that would terminate a default R"(...)"
98 // literal early.
99 std::regex invoke_pattern(
100 R"re(<|DSML|invoke name="([^"]+)">([\s\S]*?)</|DSML|invoke>)re");
101
102 auto begin = std::sregex_iterator(content.begin(), content.end(), invoke_pattern);
103 auto end = std::sregex_iterator();
104
105 for (auto it = begin; it != end; ++it) {
106 ToolCall tc;
107 tc.id = generate_uuid();
108 tc.name = (*it)[1].str();
109 tc.arguments = extract_dsml_parameters((*it)[2].str());
110 calls.push_back(std::move(tc));
111
112 logger->info("Parsed DSML invoke: {}", calls.back().name);
113 }
114 return calls;
115}
116
128std::unordered_map<std::string, std::string> Nemotron3Adapter::extract_dsml_parameters(
129 const std::string& invoke_body) const
130{
131 std::unordered_map<std::string, std::string> arguments;
132 // Custom `re` delimiter — same `)"`-in-pattern reason as the
133 // invoke regex above.
134 std::regex param_pattern(
135 R"re(<|DSML|parameter name="([^"]+)"\s+(?:string|int|bool|value)="([^"]*)"\s*/>)re");
136
137 auto begin = std::sregex_iterator(invoke_body.begin(), invoke_body.end(), param_pattern);
138 auto end = std::sregex_iterator();
139
140 for (auto it = begin; it != end; ++it) {
141 arguments[(*it)[1].str()] = (*it)[2].str();
142 }
143 return arguments;
144}
145
153std::vector<ToolCall> Nemotron3Adapter::parse_xml_function_calls(
154 const std::string& content) const
155{
156 std::vector<ToolCall> calls;
157 std::regex func_pattern(R"(<function=([^>]+)>([\s\S]*?)</function>)");
158
159 auto begin = std::sregex_iterator(content.begin(), content.end(), func_pattern);
160 auto end = std::sregex_iterator();
161
162 for (auto it = begin; it != end; ++it) {
163 std::string func_name = (*it)[1].str();
164 std::string func_body = (*it)[2].str();
165
166 auto ns = func_name.find_first_not_of(" \t");
167 auto ne = func_name.find_last_not_of(" \t");
168 if (ns != std::string::npos) {
169 func_name = func_name.substr(ns, ne - ns + 1);
170 }
171
172 ToolCall tc;
173 tc.id = generate_uuid();
174 tc.name = func_name;
175 tc.arguments = extract_xml_parameters(func_body);
176 calls.push_back(std::move(tc));
177
178 logger->info("Parsed XML tool call: {}", func_name);
179 }
180 return calls;
181}
182
194std::unordered_map<std::string, std::string> Nemotron3Adapter::extract_xml_parameters(
195 const std::string& func_body) const
196{
198 func_body, logger);
199}
200
201// ── Tool result formatting ─────────────────────────────────
202
212 const ToolCall& /*tool_call*/,
213 const std::string& result) const
214{
215 Message msg;
216 msg.role = "user";
217 msg.content = "<tool_response>\n" + result +
218 "\n</tool_response>\n\n" + TOOL_RESULT_SUFFIX;
219 return msg;
220}
221
222// ── Tool definition formatting ─────────────────────────────
223
231static nlohmann::json build_tool_defs(
232 const std::vector<std::string>& tool_jsons)
233{
234 nlohmann::json tool_defs = nlohmann::json::array();
235 for (const auto& json_str : tool_jsons) {
236 try {
237 auto j = nlohmann::json::parse(json_str);
238 tool_defs.push_back({
239 {"type", "function"},
240 {"function", {
241 {"name", j.value("name", "unknown")},
242 {"description", j.value("description", "")},
243 {"parameters", j.value("inputSchema",
244 nlohmann::json::object())}
245 }}
246 });
247 } catch (...) {
248 continue;
249 }
250 }
251 return tool_defs;
252}
253
267 const std::vector<std::string>& tool_jsons) const
268{
269 std::ostringstream out;
270 out << "# Tools\n\n"
271 << "You may call one or more functions to assist with the user query.\n"
272 << "Put your final answer OUTSIDE of any tool calls.\n\n"
273 << "Here are the available tools:\n"
274 << "<tools>\n"
275 << build_tool_defs(tool_jsons).dump(2) << "\n"
276 << "</tools>\n\n"
277 << "For each function call, emit a DSML invoke block:\n"
278 << "<|DSML|function_calls>\n"
279 << "<|DSML|invoke name=\"example.tool\">\n"
280 << "<|DSML|parameter name=\"param_name\" string=\"value\"/>\n"
281 << "</|DSML|invoke>\n"
282 << "</|DSML|function_calls>";
283 return out.str();
284}
285
286// ── Content cleaning ───────────────────────────────────────
287
304std::string Nemotron3Adapter::clean_content(const std::string& content) const {
305 std::string cleaned = std::regex_replace(content,
306 std::regex(R"(<tool_call>\s*[\s\S]*?\s*</tool_call>)"), "");
307 cleaned = std::regex_replace(cleaned,
308 std::regex(R"(<function=[^>]+>[\s\S]*?</function>)"), "");
309 cleaned = std::regex_replace(cleaned,
310 std::regex(R"(<|DSML|function_calls>[\s\S]*?</|DSML|function_calls>)"), "");
311 cleaned = std::regex_replace(cleaned,
312 std::regex(R"(<|DSML|invoke[\s\S]*?</|DSML|invoke>)"), "");
313 cleaned = std::regex_replace(cleaned,
314 std::regex(R"(</?|[^>]*>)"), "");
315 cleaned = strip_think_blocks(cleaned);
316 return cleaned;
317}
318
319} // namespace entropic
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 a <tools> JSON array, then teach DSML invoke.
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 DSML invoke calls; fall back to qwen XML, then 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).
std::string generate_uuid()
Generate a UUID v4 string.
Definition backend.cpp:840
static nlohmann::json build_tool_defs(const std::vector< std::string > &tool_jsons)
Build the OpenAI-function <tools> JSON array for injection.
Nemotron 3 chat adapter (v2.1.9, gh#47).
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 > arguments
Tool arguments as string key-value pairs.
Definition tool_call.h:34
std::string id
Unique call ID (UUID)
Definition tool_call.h:32
std::string name
Tool name (e.g. "filesystem.read_file")
Definition tool_call.h:33
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....