18#include <nlohmann/json.hpp>
57 std::vector<Message>
msgs;
59 auto arr = nlohmann::json::parse(
json_str,
nullptr,
false);
60 if (!arr.is_array()) {
return msgs; }
61 for (
const auto& obj : arr) {
63 m.
role = obj.value(
"role",
"");
64 m.content = obj.value(
"content",
"");
65 msgs.push_back(std::move(
m));
80 const char* key,
T&
dst) {
81 if (
j.contains(key)) {
dst =
j[key].get<
T>(); }
93 const nlohmann::json&
j,
94 std::unordered_map<int32_t, float>&
dst)
96 if (!
j.contains(
"logit_bias") || !
j[
"logit_bias"].is_object()) {
99 for (
auto it =
j[
"logit_bias"].
begin();
it !=
j[
"logit_bias"].end(); ++
it) {
101 dst[std::stoi(
it.key())] =
it.value().get<
float>();
102 }
catch (
const std::exception&) {
118 auto j = nlohmann::json::parse(
json_str,
nullptr,
false);
119 if (!
j.is_object()) {
return p; }
147 const std::string& default_tier) {
148 if (!
json_str) {
return default_tier; }
149 auto j = nlohmann::json::parse(
json_str,
nullptr,
false);
150 if (
j.is_object() &&
j.contains(
"tier")) {
151 return j[
"tier"].get<std::string>();
163static char*
dup(
const std::string&
s) {
175 const char* params_json,
182 auto result =
ctx->orchestrator->generate(
184 auto& out = result.raw_content.empty()
185 ? result.content : result.raw_content;
206 const char*
msgs_json,
const char* params_json,
207 void (*on_token)(
const char*,
size_t,
void*),
212 std::atomic<bool> cancel_flag(
false);
214 (std::string_view
tok) {
217 cancel_flag.store(
true, std::memory_order_release);
221 ctx->orchestrator->generate_streaming(
238 const char*
msgs_json,
const char* params_json,
245 std::atomic<bool> cancel_flag(
false);
246 std::atomic<bool>
done(
false);
250 while (!
done.load(std::memory_order_acquire)) {
252 cancel_flag.store(true, std::memory_order_release);
255 std::this_thread::sleep_for(
256 std::chrono::milliseconds(10));
261 auto result =
ctx->orchestrator->generate(
264 done.store(
true, std::memory_order_release);
267 auto& out = result.raw_content.empty()
268 ? result.content : result.raw_content;
279 char** result_json,
void* user_data) {
282 auto tier = ctx->orchestrator->route(messages);
283 *result_json =
dup(tier);
293 const char* params_json,
294 char** result_json,
void* user_data) {
296 auto tier =
extract_tier(params_json, ctx->default_tier);
302 auto result = ctx->orchestrator->generate(
303 {msg}, params, tier);
304 *result_json =
dup(result.content);
316 std::string name = j.value(
"name", std::string{});
317 if (name.empty()) { name = j.value(
"tool", std::string{}); }
319 auto it = j.find(
"arguments");
320 if (it == j.end() || !it->is_object()) { it = j.find(
"parameters"); }
321 const nlohmann::json* args =
322 (it != j.end() && it->is_object()) ? &(*it) :
nullptr;
324 std::vector<ToolCall> result;
325 if (!name.empty() && args !=
nullptr) {
327 tc.
name = std::move(name);
328 for (
const auto& [k, v] : args->items()) {
329 tc.
arguments[k] = v.is_string() ? v.get<std::string>() : v.dump();
331 result.push_back(std::move(tc));
350 const std::string kOpen =
"```json";
351 const std::string kClose =
"```";
352 auto ob = raw.find(kOpen);
353 auto bs = (ob != std::string::npos) ? (ob + kOpen.size()) : std::string::npos;
354 auto cb = (bs != std::string::npos) ? raw.find(kClose, bs) : std::string::npos;
355 bool single = (ob != std::string::npos) && (cb != std::string::npos)
356 && (raw.find(kOpen, ob + 1) == std::string::npos
357 || raw.find(kOpen, ob + 1) > cb);
358 if (!single) {
return {}; }
359 auto j = nlohmann::json::parse(raw.substr(bs, cb - bs),
nullptr,
false);
360 if (!j.is_object()) {
return {}; }
378 const nlohmann::json& obj,
379 const nlohmann::json& tools) {
381 for (
const auto& t : tools) {
382 auto name = t.value(
"name", std::string{});
383 if (name.empty()) {
continue; }
384 auto req = t.value(
"inputSchema", nlohmann::json{})
385 .value(
"required", nlohmann::json::array());
387 for (
const auto& r : req) {
389 || !obj.contains(r.get<std::string>())) {
393 if (!
ok) {
continue; }
394 if (!matched.empty()) {
return {}; }
414 const std::string& raw) {
415 const std::string kOpen =
"```json";
416 const std::string kClose =
"```";
417 auto ob = raw.find(kOpen);
418 auto bs = ob != std::string::npos ? ob + kOpen.size() : std::string::npos;
419 auto cb = bs != std::string::npos ? raw.find(kClose, bs) : std::string::npos;
420 bool single = ob != std::string::npos && cb != std::string::npos
421 && (raw.find(kOpen, ob + 1) == std::string::npos
422 || raw.find(kOpen, ob + 1) > cb);
423 if (!single) {
return std::nullopt; }
424 auto j = nlohmann::json::parse(raw.substr(bs, cb - bs),
nullptr,
false);
425 if (!j.is_object() || j.contains(
"name") || j.contains(
"tool")) {
447 const std::string& raw,
448 const std::string& tools_json) {
450 if (!j_opt) {
return {}; }
451 auto tools = nlohmann::json::parse(tools_json,
nullptr,
false);
452 auto name = tools.is_array()
454 if (name.empty()) {
return {}; }
456 tc.
name = std::move(name);
457 for (
const auto& [k, v] : j_opt->items()) {
458 tc.
arguments[k] = v.is_string() ? v.get<std::string>() : v.dump();
460 return {std::move(tc)};
478 std::vector<ToolCall>& calls) {
479 if (!calls.empty()) {
return; }
481 if (fb.empty() && llama !=
nullptr) {
484 if (!fb.empty()) { calls = std::move(fb); }
503 const std::string& raw,
504 const std::string& tier,
505 std::string& out_cleaned,
506 std::vector<ToolCall>& out_calls) {
508 if (adapter ==
nullptr) { out_cleaned = raw;
return; }
509 auto p = adapter->parse_tool_calls(raw);
510 out_cleaned = std::move(p.cleaned_content);
511 out_calls = std::move(p.tool_calls);
545 char** tool_calls_json,
548 std::string raw_str = raw ? raw :
"";
549 auto tier = ctx->orchestrator->last_used_tier();
550 if (tier.empty()) { tier = ctx->default_tier; }
552 ctx->orchestrator->get_backend(tier));
558 llama, ctx->orchestrator->get_adapter(tier), raw_str);
559 std::string cleaned_str = std::move(parsed.content);
560 std::vector<ToolCall> calls = std::move(parsed.tool_calls);
564 *cleaned =
dup(cleaned_str);
575 const char* tool_calls_json,
577 if (!tool_calls_json) {
return 1; }
578 auto tc = nlohmann::json::parse(tool_calls_json,
nullptr,
false);
579 return (tc.is_array() && !tc.empty()) ? 0 : 1;
594 const std::string& default_tier,
597 if (out_context) { *out_context = ctx; }
599 InferenceInterface iface;
607 iface.free_fn = free;
608 iface.backend_data = ctx;
609 iface.orchestrator_data = ctx;
610 iface.adapter_data = ctx;
ChatAdapter concrete base class.
LlamaCppBackend — common llama.cpp patterns (15% layer).
const std::string & active_tools_json() const
Return the tool definitions staged for the current turn.
Multi-model lifecycle and routing orchestrator.
ChatAdapter * get_adapter(const std::string &tier_name) const
Get adapter for a tier.
Configuration structs with defaults.
int * cancel
Consumer's cancel flag, nullable.
Factory for building InferenceInterface from a ModelOrchestrator.
LlamaCppBackend — llama.cpp C API integration.
Message struct for conversation history.
Activate model on GPU (WARM → ACTIVE).
static void assign_if_present(const nlohmann::json &j, const char *key, T &dst)
Conditionally assign a typed JSON field into a destination.
static int iface_generate_with_cancel(const char *msgs_json, const char *params_json, char **result_json, int *cancel, void *user_data)
Batch generate with cancel via orchestrator (gh#81, v2.4.2).
static int iface_parse_tool_calls(const char *raw, char **cleaned, char **tool_calls_json, void *user_data)
Parse tool calls from raw model output (gh#87 3b).
@ ok
Tool dispatched, returned non-empty content.
std::string serialize_tool_calls(const std::vector< ToolCall > &calls)
Serialize parsed tool calls to the C-ABI JSON array form.
static std::vector< Message > parse_msgs(const char *json_str)
Parse JSON message array into Message vector.
@ optional
optional: every escape asks the host's path approver
static GenerationParams parse_params(const char *json_str)
Parse generation params from JSON string.
static int iface_is_complete(const char *, const char *tool_calls_json, void *)
Check if response is complete (no pending tool calls).
static void apply_fenced_fallbacks(const std::string &raw, LlamaCppBackend *llama, std::vector< ToolCall > &calls)
Apply fenced-json and fenced-args-only fallback parsers (gh#122, gh#127).
@ tier
The TIER's identity frontmatter grammar: stem (gh#154).
static int iface_generate(const char *msgs_json, const char *params_json, char **result_json, void *user_data)
Generate via orchestrator.
static void parse_logit_bias_into(const nlohmann::json &j, std::unordered_map< int32_t, float > &dst)
Populate logit_bias map from a JSON object of token→bias.
static int iface_route(const char *msgs_json, char **result_json, void *user_data)
Route messages to tier via orchestrator.
static std::string extract_tier(const char *json_str, const std::string &default_tier)
Extract tier name from params JSON, falling back to default.
static void parse_via_adapter(ModelOrchestrator *orch, const std::string &raw, const std::string &tier, std::string &out_cleaned, std::vector< ToolCall > &out_calls)
Parse tool calls via the adapter for the given tier (gh#89).
static std::vector< ToolCall > try_fenced_json_call(const std::string &raw)
Extract a tool call from a single fenced json block in raw model output (gh#122).
void destroy_orchestrator_interface(InterfaceContext *context)
Free a context returned by build_orchestrator_interface().
static int iface_generate_stream(const char *msgs_json, const char *params_json, void(*on_token)(const char *, size_t, void *), void *token_ud, int *cancel, void *user_data)
Streaming generate via orchestrator.
static std::optional< nlohmann::json > extract_args_only_fence(const std::string &raw)
Extract and validate the fenced JSON block for args-only inference.
static std::vector< ToolCall > tc_from_json_obj(const nlohmann::json &j)
Build a ToolCall from a parsed JSON object with name/tool + arguments/parameters.
static int iface_complete(const char *prompt, const char *params_json, char **result_json, void *user_data)
Raw text completion via orchestrator.
static char * dup(const std::string &s)
Heap-allocate a C string copy.
ParsedModelResponse parse_model_response(LlamaCppBackend *llama, ChatAdapter *adapter, const std::string &raw)
Parse a raw emission: template first, adapter second.
static std::vector< ToolCall > try_fenced_args_object_call(const std::string &raw, const std::string &tools_json)
Try to synthesise a tool call from a fenced args-only JSON object (gh#127).
InferenceInterface build_orchestrator_interface(ModelOrchestrator *orchestrator, const std::string &default_tier, InterfaceContext **out_context)
Build an InferenceInterface wired to an orchestrator.
static std::string find_unique_args_match(const nlohmann::json &obj, const nlohmann::json &tools)
Find the unique tool whose required params are all present in obj.
ModelOrchestrator — multi-model lifecycle and routing.
One template-first / adapter-second parse rule for raw model output.
Generation parameters for a single inference call.
int max_tokens
Maximum tokens to generate.
Holds orchestrator + tier for C callback user_data.
ModelOrchestrator * orchestrator
Orchestrator pointer.
std::string default_tier
Default tier name.
A message in a conversation.
std::string content
Message text content (always populated)
std::string role
Message role.