Entropic 2.9.4
Local-first agentic inference engine
Loading...
Searching...
No Matches
interface_factory.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
12#include <entropic/inference/adapters/adapter_base.h> // gh#88 recovery
13
14#include "llama_cpp_backend.h" // gh#87 3b: common_chat parse routing
15#include "tool_call_serialize.h" // gh#93: shared (typed) tool-call serialization
16
17#include <nlohmann/json.hpp>
18
19#include <atomic>
20#include <chrono>
21#include <cstdlib>
22#include <cstring>
23#include <string>
24#include <thread>
25#include <vector>
26
27namespace entropic {
28
29// ── Context struct for callbacks ───────────────────────────
30
45
46// ── JSON helpers ───────────────────────────────────────────
47
55static std::vector<Message> parse_msgs(const char* json_str) {
56 std::vector<Message> msgs;
57 if (!json_str) { return msgs; }
58 auto arr = nlohmann::json::parse(json_str, nullptr, false);
59 if (!arr.is_array()) { return msgs; }
60 for (const auto& obj : arr) {
61 Message m;
62 m.role = obj.value("role", "");
63 m.content = obj.value("content", "");
64 msgs.push_back(std::move(m));
65 }
66 return msgs;
67}
68
77template <typename T>
78static void assign_if_present(const nlohmann::json& j,
79 const char* key, T& dst) {
80 if (j.contains(key)) { dst = j[key].get<T>(); }
81}
82
92 const nlohmann::json& j,
93 std::unordered_map<int32_t, float>& dst)
94{
95 if (!j.contains("logit_bias") || !j["logit_bias"].is_object()) {
96 return;
97 }
98 for (auto it = j["logit_bias"].begin(); it != j["logit_bias"].end(); ++it) {
99 try {
100 dst[std::stoi(it.key())] = it.value().get<float>();
101 } catch (const std::exception&) {
102 // skip un-parseable keys
103 }
104 }
105}
106
114static GenerationParams parse_params(const char* json_str) {
116 if (!json_str) { return p; }
117 auto j = nlohmann::json::parse(json_str, nullptr, false);
118 if (!j.is_object()) { return p; }
119 assign_if_present(j, "max_tokens", p.max_tokens);
120 assign_if_present(j, "temperature", p.temperature);
121 assign_if_present(j, "grammar_key", p.grammar_key);
122 assign_if_present(j, "enable_thinking", p.enable_thinking);
123 assign_if_present(j, "top_p", p.top_p);
124 assign_if_present(j, "top_k", p.top_k);
125 assign_if_present(j, "min_p", p.min_p);
126 assign_if_present(j, "presence_penalty", p.presence_penalty);
127 assign_if_present(j, "frequency_penalty",p.frequency_penalty);
128 assign_if_present(j, "repeat_penalty", p.repeat_penalty);
129 assign_if_present(j, "seed", p.seed);
130 assign_if_present(j, "tools", p.tools); // gh#87 3b
132 return p;
133}
134
143static std::string extract_tier(const char* json_str,
144 const std::string& default_tier) {
145 if (!json_str) { return default_tier; }
146 auto j = nlohmann::json::parse(json_str, nullptr, false);
147 if (j.is_object() && j.contains("tier")) {
148 return j["tier"].get<std::string>();
149 }
150 return default_tier;
151}
152
160static char* dup(const std::string& s) {
161 return strdup(s.c_str());
162}
163
164// ── C-callable wrappers ────────────────────────────────────
165
171static int iface_generate(const char* msgs_json,
172 const char* params_json,
173 char** result_json,
174 void* user_data) {
175 auto* ctx = static_cast<InterfaceContext*>(user_data);
176 auto messages = parse_msgs(msgs_json);
177 auto params = parse_params(params_json);
178 auto tier = extract_tier(params_json, ctx->default_tier);
179 auto result = ctx->orchestrator->generate(
180 messages, params, tier);
181 auto& out = result.raw_content.empty()
182 ? result.content : result.raw_content;
183 *result_json = dup(out);
184 return 0;
185}
186
203 const char* msgs_json, const char* params_json,
204 void (*on_token)(const char*, size_t, void*),
205 void* token_ud, int* cancel, void* user_data) {
206 auto* ctx = static_cast<InterfaceContext*>(user_data);
207 auto messages = parse_msgs(msgs_json);
208 auto params = parse_params(params_json);
209 std::atomic<bool> cancel_flag(false);
210 auto cb = [on_token, token_ud, cancel, &cancel_flag]
211 (std::string_view tok) {
212 on_token(tok.data(), tok.size(), token_ud);
213 if (cancel != nullptr && *cancel != 0) {
214 cancel_flag.store(true, std::memory_order_release);
215 }
216 };
217 auto tier = extract_tier(params_json, ctx->default_tier);
218 ctx->orchestrator->generate_streaming(
219 messages, params, cb, cancel_flag, tier);
220 return 0;
221}
222
235 const char* msgs_json, const char* params_json,
236 char** result_json, int* cancel, void* user_data) {
237 auto* ctx = static_cast<InterfaceContext*>(user_data);
238 auto messages = parse_msgs(msgs_json);
239 auto params = parse_params(params_json);
240 auto tier = extract_tier(params_json, ctx->default_tier);
241
242 std::atomic<bool> cancel_flag(false);
243 std::atomic<bool> done(false);
244 std::thread poller;
245 if (cancel != nullptr) {
246 poller = std::thread([cancel, &cancel_flag, &done]() {
247 while (!done.load(std::memory_order_acquire)) {
248 if (*cancel != 0) {
249 cancel_flag.store(true, std::memory_order_release);
250 return;
251 }
252 std::this_thread::sleep_for(
253 std::chrono::milliseconds(10));
254 }
255 });
256 }
257
258 auto result = ctx->orchestrator->generate(
259 messages, params, cancel_flag, tier);
260
261 done.store(true, std::memory_order_release);
262 if (poller.joinable()) { poller.join(); }
263
264 auto& out = result.raw_content.empty()
265 ? result.content : result.raw_content;
266 *result_json = dup(out);
267 return 0;
268}
269
275static int iface_route(const char* msgs_json,
276 char** result_json, void* user_data) {
277 auto* ctx = static_cast<InterfaceContext*>(user_data);
278 auto messages = parse_msgs(msgs_json);
279 auto tier = ctx->orchestrator->route(messages);
280 *result_json = dup(tier);
281 return 0;
282}
283
289static int iface_complete(const char* prompt,
290 const char* params_json,
291 char** result_json, void* user_data) {
292 auto* ctx = static_cast<InterfaceContext*>(user_data);
293 auto tier = extract_tier(params_json, ctx->default_tier);
294 Message msg;
295 msg.role = "user";
296 msg.content = prompt;
297 GenerationParams params{};
298 params.max_tokens = 1;
299 auto result = ctx->orchestrator->generate(
300 {msg}, params, tier);
301 *result_json = dup(result.content);
302 return 0;
303}
304
317static int iface_parse_tool_calls(const char* raw,
318 char** cleaned,
319 char** tool_calls_json,
320 void* user_data) {
321 auto* ctx = static_cast<InterfaceContext*>(user_data);
322 std::string raw_str = raw ? raw : "";
323
324 auto tier = ctx->orchestrator->last_used_tier();
325 if (tier.empty()) { tier = ctx->default_tier; }
326 auto* llama = dynamic_cast<LlamaCppBackend*>(
327 ctx->orchestrator->get_backend(tier));
328
329 if (llama != nullptr && llama->common_chat_parse_reliable()) {
330 auto parsed = llama->parse_response(raw_str);
331 apply_action_envelope_recovery(parsed.tool_calls, raw_str); // gh#88
332 *cleaned = dup(parsed.content);
333 *tool_calls_json = dup(serialize_tool_calls(parsed.tool_calls));
334 return 0;
335 }
336
337 // gh#89: fall back on the SAME routed tier as the reliable branch above
338 // (last_used_tier), not default_tier — a routed non-default autoparser tier
339 // was parsing with the wrong tier's adapter.
340 auto* adapter = ctx->orchestrator->get_adapter(tier);
341 if (adapter == nullptr) {
342 *cleaned = dup(raw_str);
343 *tool_calls_json = dup("[]");
344 return 0;
345 }
346 auto parsed = adapter->parse_tool_calls(raw_str);
347 *cleaned = dup(parsed.cleaned_content);
348 *tool_calls_json = dup(serialize_tool_calls(parsed.tool_calls));
349 return 0;
350}
351
357static int iface_is_complete(const char* /*content*/,
358 const char* tool_calls_json,
359 void* /*user_data*/) {
360 if (!tool_calls_json) { return 1; }
361 auto tc = nlohmann::json::parse(tool_calls_json, nullptr, false);
362 return (tc.is_array() && !tc.empty()) ? 0 : 1;
363}
364
365// ── Factory ────────────────────────────────────────────────
366
376 ModelOrchestrator* orchestrator,
377 const std::string& default_tier,
378 InterfaceContext** out_context) {
379 auto* ctx = new InterfaceContext{orchestrator, default_tier};
380 if (out_context) { *out_context = ctx; }
381
382 InferenceInterface iface;
383 iface.generate = iface_generate;
384 iface.generate_cancellable = iface_generate_with_cancel; // gh#81, v2.4.2
385 iface.generate_stream = iface_generate_stream;
386 iface.route = iface_route;
387 iface.complete = iface_complete;
388 iface.parse_tool_calls = iface_parse_tool_calls;
389 iface.is_response_complete = iface_is_complete;
390 iface.free_fn = free;
391 iface.backend_data = ctx;
392 iface.orchestrator_data = ctx;
393 iface.adapter_data = ctx;
394 return iface;
395}
396
403 delete context;
404}
405
406} // namespace entropic
ChatAdapter concrete base class.
LlamaCppBackend — common llama.cpp patterns (15% layer).
CommonChatResult parse_response(const std::string &raw) const
Parse a raw model emission via the last captured render params.
Multi-model lifecycle and routing orchestrator.
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).
void apply_action_envelope_recovery(std::vector< ToolCall > &calls, const std::string &raw)
gh#88: substitute recovered bare-JSON calls when a reliable (PEG_GEMMA4 / gemma) parse produced none;...
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 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.
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 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.
InferenceInterface build_orchestrator_interface(ModelOrchestrator *orchestrator, const std::string &default_tier, InterfaceContext **out_context)
Build an InferenceInterface wired to an orchestrator.
ModelOrchestrator — multi-model lifecycle and routing.
Generation parameters for a single inference call.
Definition config.h:302
int top_k
Top-K sampling.
Definition config.h:305
std::unordered_map< int32_t, float > logit_bias
Per-token logit bias map (gh#23 MVP item 4).
Definition config.h:336
float repeat_penalty
Repetition penalty.
Definition config.h:306
std::string tools
Active tool definitions for this turn, as an MCP tool-list JSON array ([{name, description,...
Definition config.h:411
float temperature
Sampling temperature.
Definition config.h:303
std::string grammar_key
Grammar registry key.
Definition config.h:364
float frequency_penalty
Frequency-penalty term in llama.cpp's penalties sampler (gh#23 MVP item 3).
Definition config.h:349
float presence_penalty
Presence-penalty term in llama.cpp's penalties sampler (gh#23 MVP item 2).
Definition config.h:322
bool enable_thinking
Enable <think> blocks (false if reasoning_budget == 0)
Definition config.h:358
float min_p
Min-p nucleus sampling threshold (gh#23 MVP item 1).
Definition config.h:315
int max_tokens
Maximum tokens to generate.
Definition config.h:351
float top_p
Nucleus sampling threshold.
Definition config.h:304
int seed
RNG seed for reproducible sampling.
Definition config.h:356
Holds orchestrator + tier for C callback user_data.
ModelOrchestrator * orchestrator
Orchestrator pointer.
std::string default_tier
Default tier name.
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
Shared serialization of parsed tool calls to the C-ABI JSON array form.