15#include <nlohmann/json.hpp>
29constexpr const char* TOOL_RESULT_SUFFIX =
30 "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));
59std::string tool_name_from_json(
const nlohmann::json& j) {
60 for (
const char* key : {
"name",
"tool_name",
"function",
"function_name"}) {
61 if (j.contains(key) && j[key].is_string()) {
62 return j[key].get<std::string>();
81std::optional<ToolCall> tool_call_from_json(
const nlohmann::json& j) {
82 std::string name = tool_name_from_json(j);
83 if (name.empty()) {
return std::nullopt; }
86 tc.name = std::move(name);
87 auto args = j.value(
"arguments", j.value(
"parameters", nlohmann::json::object()));
88 for (
auto& [k, v] : args.items()) { tc.arguments[k] = v.dump(); }
103 : tier_name_(std::move(tier_name))
104 , identity_prompt_(std::move(identity_prompt))
119 const std::string& base_prompt,
120 const std::vector<std::string>& tool_jsons)
const
124 if (!base_prompt.empty()) {
125 prompt +=
"\n\n" + base_prompt;
128 if (!tool_jsons.empty()) {
130 for (
const auto& json_str : tool_jsons) {
132 auto j = nlohmann::json::parse(json_str);
133 std::string name = j.value(
"name",
"");
134 auto dot = name.find(
'.');
135 if (dot != std::string::npos) {
160 const std::string& result)
const
164 msg.
content =
"Tool `" + tool_call.
name +
"` returned:\n\n" +
165 result +
"\n\n" + TOOL_RESULT_SUFFIX;
183 const std::string& content,
184 const std::vector<ToolCall>& tool_calls)
const
187 if (!tool_calls.empty()) {
192 if (content.find(
"<think>") != std::string::npos &&
193 content.find(
"</think>") == std::string::npos)
200 return !stripped.empty();
213 const std::string& content)
const
215 std::vector<ToolCall> calls;
234 R
"((?:<tool_call>|<\|tool_call\|?>|<\|im_start\|>tool_call)\s*)"
235 R"(([\s\S]*?)\s*</tool_call>)");
237 auto begin = std::sregex_iterator(content.begin(), content.end(), pattern);
238 auto end = std::sregex_iterator();
240 for (
auto it = begin; it != end; ++it) {
241 std::string json_str = (*it)[1].str();
244 calls.push_back(*parsed);
245 logger->info(
"Parsed tagged tool call: {}", parsed->name);
253 "Tagged tool_call matched but JSON failed to parse: {}",
264 && (content.find(
"<tool_call>") != std::string::npos
265 || content.find(
"<|tool_call") != std::string::npos
266 || content.find(
"<|im_start|>tool_call") != std::string::npos)) {
268 "Content contains tool_call markup but no tagged calls "
269 "were extracted — possible tag/encoding mismatch. "
270 "Raw content length={}", content.size());
285 const std::string& content)
const
287 std::vector<ToolCall> calls;
288 std::istringstream stream(content);
291 while (std::getline(stream, line)) {
293 size_t start = line.find_first_not_of(
" \t");
294 if (start == std::string::npos)
continue;
295 std::string_view stripped(line.data() + start, line.size() - start);
299 if (stripped.front() !=
'{'
300 || (stripped.find(
"name") == std::string_view::npos)) {
305 auto j = nlohmann::json::parse(stripped);
306 if (
auto tc = tool_call_from_json(j)) {
307 calls.push_back(*tc);
327 std::regex pattern(R
"(<think>([\s\S]*?)</think>)");
329 auto begin = std::sregex_iterator(content.begin(), content.end(), pattern);
330 auto end = std::sregex_iterator();
332 for (
auto it = begin; it != end; ++it) {
333 if (!result.empty()) result +=
"\n";
334 result += (*it)[1].str();
347 std::regex pattern(R
"(<think>[\s\S]*?</think>)");
348 std::string result = std::regex_replace(content, pattern, "");
351 size_t start = result.find_first_not_of(
" \t\n\r");
352 if (start == std::string::npos)
return "";
353 size_t end_pos = result.find_last_not_of(
" \t\n\r");
354 return result.substr(start, end_pos - start + 1);
367 const std::string& fixed) {
368 auto j = nlohmann::json::parse(fixed);
369 return tool_call_from_json(j);
380 const std::string& json_str) {
381 std::regex name_pattern(R
"re("name"\s*:\s*"([^"]+)")re");
383 if (!std::regex_search(json_str, match, name_pattern)) {
388 tc.
name = match[1].str();
403 const std::string& json_str)
const
406 std::string fixed = std::regex_replace(json_str, std::regex(R
"(,\s*\})"), "}");
407 fixed = std::regex_replace(fixed, std::regex(R
"(,\s*\])"), "]");
408 std::replace(fixed.begin(), fixed.end(),
'\'',
'"');
410 logger->info(
"JSON recovery attempt: {} chars", json_str.size());
429 const std::vector<std::string>& tool_jsons)
const
431 std::ostringstream out;
432 out <<
"## Tools\n\n"
433 <<
"Call tools with: `<tool_call>{\"name\": \"tool.name\", \"arguments\": {...}}</tool_call>`\n"
434 <<
"Batch independent calls in one response with multiple `<tool_call>` blocks.\n\n";
436 for (
const auto& json_str : tool_jsons) {
438 auto j = nlohmann::json::parse(json_str);
439 out <<
"### " << j.value(
"name",
"unknown") <<
"\n"
440 << j.value(
"description",
"No description") <<
"\n\n"
441 <<
"Schema:\n```json\n"
442 << j.value(
"inputSchema", nlohmann::json::object()).dump(2)
445 out <<
"### (malformed tool definition)\n\n";
461 const std::string& json_str)
const
464 auto j = nlohmann::json::parse(json_str);
465 if (
auto tc = tool_call_from_json(j)) {
485 const std::string& base_system,
498 const std::vector<ContentPart>& parts)
const {
499 nlohmann::json arr = nlohmann::json::array();
500 for (
const auto& part : parts) {
503 obj[
"type"] =
"text";
504 obj[
"text"] = part.text;
506 obj[
"type"] =
"image";
507 if (!part.image_path.empty()) {
508 obj[
"path"] = part.image_path;
510 if (!part.image_url.empty()) {
511 obj[
"url"] = part.image_url;
514 arr.push_back(std::move(obj));
ChatAdapter concrete base class.
std::string format_system_prompt(const std::string &base_prompt, const std::vector< std::string > &tool_jsons) const
Assemble system prompt: identity + context + tools.
ChatAdapter(std::string tier_name, std::string identity_prompt)
Construct adapter with tier identity.
std::unordered_set< std::string > tool_prefixes_
Known tool prefixes.
std::optional< ToolCall > try_recover_json(const std::string &json_str) const
Attempt JSON recovery on malformed tool call string.
virtual std::string format_content_parts(const std::vector< ContentPart > &parts) const
Convert multimodal content parts to adapter-specific format.
virtual std::string format_system_with_vision(const std::string &base_system, bool has_vision) const
Format system prompt with optional vision context.
std::optional< ToolCall > parse_single_tool_call(const std::string &json_str) const
Parse a single JSON tool call string.
std::vector< ToolCall > parse_tagged_tool_calls(const std::string &content) const
Parse <tool_call>JSON</tool_call> tagged blocks.
std::vector< ToolCall > parse_bare_json_tool_calls(const std::string &content) const
Parse bare JSON lines containing "name" key.
virtual std::string format_tools(const std::vector< std::string > &tool_jsons) const
Format tool definitions for injection into system prompt.
bool is_response_complete(const std::string &content, const std::vector< ToolCall > &tool_calls) const
Check if response represents task completion.
std::string strip_think_blocks(const std::string &content) const
Strip all <think>...</think> blocks from content.
std::string identity_prompt_
Assembled identity prompt.
virtual Message format_tool_result(const ToolCall &tool_call, const std::string &result) const
Format a tool result as a user message.
std::string extract_thinking(const std::string &content) const
Extract <think>...</think> content.
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).
@ TEXT
Plain text content.
static std::optional< ToolCall > parse_recovered_tool_call(const std::string &fixed)
Parse a brace/quote-fixed JSON string into a ToolCall.
std::string generate_uuid()
Generate a UUID v4 string.
static std::optional< ToolCall > regex_recovered_tool_call(const std::string &json_str)
Last-ditch recovery: pull a tool name out via regex.
A message in a conversation.
std::string content
Message text content (always populated)
std::string role
Message role.