18#include <nlohmann/json.hpp>
56static std::vector<Message>
parse_msgs(
const char* json_str) {
57 std::vector<Message> msgs;
58 if (!json_str) {
return 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&) {
117 if (!json_str) {
return p; }
118 auto j = nlohmann::json::parse(json_str,
nullptr,
false);
119 if (!j.is_object()) {
return p; }
145 const std::string& default_tier) {
146 if (!json_str) {
return default_tier; }
147 auto j = nlohmann::json::parse(json_str,
nullptr,
false);
148 if (j.is_object() && j.contains(
"tier")) {
149 return j[
"tier"].get<std::string>();
161static char*
dup(
const std::string& s) {
162 return strdup(s.c_str());
173 const char* params_json,
179 auto tier =
extract_tier(params_json, ctx->default_tier);
180 auto result = ctx->orchestrator->generate(
181 messages, params, tier);
182 auto& out = result.raw_content.empty()
183 ? result.content : result.raw_content;
184 *result_json =
dup(out);
204 const char* msgs_json,
const char* params_json,
205 void (*on_token)(
const char*,
size_t,
void*),
206 void* token_ud,
int* cancel,
void* user_data) {
210 std::atomic<bool> cancel_flag(
false);
211 auto cb = [on_token, token_ud, cancel, &cancel_flag]
212 (std::string_view tok) {
213 on_token(tok.data(), tok.size(), token_ud);
214 if (cancel !=
nullptr && *cancel != 0) {
215 cancel_flag.store(
true, std::memory_order_release);
218 auto tier =
extract_tier(params_json, ctx->default_tier);
219 ctx->orchestrator->generate_streaming(
220 messages, params, cb, cancel_flag, tier);
236 const char* msgs_json,
const char* params_json,
237 char** result_json,
int* cancel,
void* user_data) {
241 auto tier =
extract_tier(params_json, ctx->default_tier);
243 std::atomic<bool> cancel_flag(
false);
244 std::atomic<bool> done(
false);
246 if (cancel !=
nullptr) {
247 poller = std::thread([cancel, &cancel_flag, &done]() {
248 while (!done.load(std::memory_order_acquire)) {
250 cancel_flag.store(true, std::memory_order_release);
253 std::this_thread::sleep_for(
254 std::chrono::milliseconds(10));
259 auto result = ctx->orchestrator->generate(
260 messages, params, cancel_flag, tier);
262 done.store(
true, std::memory_order_release);
263 if (poller.joinable()) { poller.join(); }
265 auto& out = result.raw_content.empty()
266 ? result.content : result.raw_content;
267 *result_json =
dup(out);
277 char** result_json,
void* user_data) {
280 auto tier = ctx->orchestrator->route(messages);
281 *result_json =
dup(tier);
291 const char* params_json,
292 char** result_json,
void* user_data) {
294 auto tier =
extract_tier(params_json, ctx->default_tier);
300 auto result = ctx->orchestrator->generate(
301 {msg}, params, tier);
302 *result_json =
dup(result.content);
314 std::string name = j.value(
"name", std::string{});
315 if (name.empty()) { name = j.value(
"tool", std::string{}); }
317 auto it = j.find(
"arguments");
318 if (it == j.end() || !it->is_object()) { it = j.find(
"parameters"); }
319 const nlohmann::json* args =
320 (it != j.end() && it->is_object()) ? &(*it) :
nullptr;
322 std::vector<ToolCall> result;
323 if (!name.empty() && args !=
nullptr) {
325 tc.
name = std::move(name);
326 for (
const auto& [k, v] : args->items()) {
327 tc.
arguments[k] = v.is_string() ? v.get<std::string>() : v.dump();
329 result.push_back(std::move(tc));
348 const std::string kOpen =
"```json";
349 const std::string kClose =
"```";
350 auto ob = raw.find(kOpen);
351 auto bs = (ob != std::string::npos) ? (ob + kOpen.size()) : std::string::npos;
352 auto cb = (bs != std::string::npos) ? raw.find(kClose, bs) : std::string::npos;
353 bool single = (ob != std::string::npos) && (cb != std::string::npos)
354 && (raw.find(kOpen, ob + 1) == std::string::npos
355 || raw.find(kOpen, ob + 1) > cb);
356 if (!single) {
return {}; }
357 auto j = nlohmann::json::parse(raw.substr(bs, cb - bs),
nullptr,
false);
358 if (!j.is_object()) {
return {}; }
376 const nlohmann::json& obj,
377 const nlohmann::json& tools) {
379 for (
const auto& t : tools) {
380 auto name = t.value(
"name", std::string{});
381 if (name.empty()) {
continue; }
382 auto req = t.value(
"inputSchema", nlohmann::json{})
383 .value(
"required", nlohmann::json::array());
385 for (
const auto& r : req) {
387 || !obj.contains(r.get<std::string>())) {
391 if (!
ok) {
continue; }
392 if (!matched.empty()) {
return {}; }
412 const std::string& raw) {
413 const std::string kOpen =
"```json";
414 const std::string kClose =
"```";
415 auto ob = raw.find(kOpen);
416 auto bs = ob != std::string::npos ? ob + kOpen.size() : std::string::npos;
417 auto cb = bs != std::string::npos ? raw.find(kClose, bs) : std::string::npos;
418 bool single = ob != std::string::npos && cb != std::string::npos
419 && (raw.find(kOpen, ob + 1) == std::string::npos
420 || raw.find(kOpen, ob + 1) > cb);
421 if (!single) {
return std::nullopt; }
422 auto j = nlohmann::json::parse(raw.substr(bs, cb - bs),
nullptr,
false);
423 if (!j.is_object() || j.contains(
"name") || j.contains(
"tool")) {
445 const std::string& raw,
446 const std::string& tools_json) {
448 if (!j_opt) {
return {}; }
449 auto tools = nlohmann::json::parse(tools_json,
nullptr,
false);
450 auto name = tools.is_array()
452 if (name.empty()) {
return {}; }
454 tc.
name = std::move(name);
455 for (
const auto& [k, v] : j_opt->items()) {
456 tc.
arguments[k] = v.is_string() ? v.get<std::string>() : v.dump();
458 return {std::move(tc)};
476 std::vector<ToolCall>& calls) {
477 if (!calls.empty()) {
return; }
479 if (fb.empty() && llama !=
nullptr) {
482 if (!fb.empty()) { calls = std::move(fb); }
501 const std::string& raw,
502 const std::string& tier,
503 std::string& out_cleaned,
504 std::vector<ToolCall>& out_calls) {
506 if (adapter ==
nullptr) { out_cleaned = raw;
return; }
507 auto p = adapter->parse_tool_calls(raw);
508 out_cleaned = std::move(p.cleaned_content);
509 out_calls = std::move(p.tool_calls);
543 char** tool_calls_json,
546 std::string raw_str = raw ? raw :
"";
547 auto tier = ctx->orchestrator->last_used_tier();
548 if (tier.empty()) { tier = ctx->default_tier; }
550 ctx->orchestrator->get_backend(tier));
556 llama, ctx->orchestrator->get_adapter(tier), raw_str);
557 std::string cleaned_str = std::move(parsed.content);
558 std::vector<ToolCall> calls = std::move(parsed.tool_calls);
562 *cleaned =
dup(cleaned_str);
573 const char* tool_calls_json,
575 if (!tool_calls_json) {
return 1; }
576 auto tc = nlohmann::json::parse(tool_calls_json,
nullptr,
false);
577 return (tc.is_array() && !tc.empty()) ? 0 : 1;
592 const std::string& default_tier,
595 if (out_context) { *out_context = ctx; }
597 InferenceInterface iface;
605 iface.free_fn = free;
606 iface.backend_data = ctx;
607 iface.orchestrator_data = ctx;
608 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.
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.
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).
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.
std::unordered_map< int32_t, float > logit_bias
Per-token logit bias map (gh#23 MVP item 4).
float repeat_penalty
Repetition penalty.
std::string tools
Active tool definitions for this turn, as an MCP tool-list JSON array ([{name, description,...
float temperature
Sampling temperature.
std::string grammar_key
Grammar registry key.
float frequency_penalty
Frequency-penalty term in llama.cpp's penalties sampler (gh#23 MVP item 3).
float presence_penalty
Presence-penalty term in llama.cpp's penalties sampler (gh#23 MVP item 2).
bool enable_thinking
Enable <think> blocks (false if reasoning_budget == 0)
float min_p
Min-p nucleus sampling threshold (gh#23 MVP item 1).
int max_tokens
Maximum tokens to generate.
float top_p
Nucleus sampling threshold.
int seed
RNG seed for reproducible sampling.
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.