File size: 10,939 Bytes
c97bd71
 
 
 
 
 
 
 
 
 
 
2b2bae2
 
c97bd71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b2bae2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c97bd71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b2bae2
 
 
 
 
 
 
 
c97bd71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b2bae2
 
 
c97bd71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b2bae2
 
 
 
 
c97bd71
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2b2bae2
 
 
 
 
c97bd71
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
// ============================================================================
//  SyFox core C API — thin shared-library wrapper (server bridge).
//  All decisions come from the SI substrate; this file only marshals JSON
//  and exposes the SAME boundary knobs the CLI already has (v1.0 -> v3.0):
//
//    M1  energy-norm measurement gain      -> syfox_engine_set_energy_norm
//    v1  SI salience gating + Miller window -> syfox_engine_set_source_modes
//    M5  deterministic OMP parallel settle -> syfox_engine_set_parallel_settle
//    v2.2 trigram lane policy              -> syfox_engine_set_ngrams
//    v2.2 script routing                   -> syfox_detect_script
//    M3  machine-auditable evidence        -> syfox_decide_ex(opts.evidence)
//    v3.2 semantic layer + retrieval + hierarchy -> set_semantics / set_retrieval /
//            set_retrieval_topk / set_retrieval_dose / set_hierarchy + opts keys
//
//  No transformer, no classifier, no physics changes: si_substrate.hpp is
//  untouched; every knob here exists because the CLI exposes it too.
// ============================================================================
#include "core/syfox.hpp"
#include "core/script.hpp"
#include "core/ngram.hpp"

#include <cstring>
#include <string>

using syfox::Engine;

namespace { bool truthy(const sfx::JV& v) {
    return (v.is_bool() && v.b) || (v.is_num() && v.num != 0.0);
} }

extern "C" {

struct SyFoxHandle { Engine* eng; };

SyFoxHandle* syfox_engine_create(const char* model_dir) {
    if (!model_dir) return nullptr;
    SyFoxHandle* h = new SyFoxHandle();
    h->eng = new Engine();
    h->eng->load_model(model_dir);
    return h;
}

void syfox_engine_free(SyFoxHandle* h) {
    if (!h) return;
    delete h->eng;
    delete h;
}

const char* syfox_core_version(void) { return syfox::VERSION; }

// -- engine-scoped knobs (identical semantics to the CLI flags) -------------

void syfox_engine_set_energy_norm(SyFoxHandle* h, int on) {
    if (h) h->eng->set_energy_norm(on != 0);          // M1: measurement gain
}

void syfox_engine_set_source_modes(SyFoxHandle* h, int salience_gating, int miller_window) {
    if (h) h->eng->substrate().set_source_modes(salience_gating != 0, miller_window != 0);
}

void syfox_engine_set_parallel_settle(SyFoxHandle* h, int on, int threads) {
    if (!h) return;
#if defined(_OPENMP)
    if (threads > 0) omp_set_num_threads(threads);
#endif
    h->eng->substrate().set_parallel_settle(on != 0); // M5: OMP builds only
}

void syfox_engine_set_ngrams(SyFoxHandle* h, int on) {
    (void)h;                                          // process-global policy
    si::norm::grams_enabled() = (on != 0);            // v2.2 trigram lanes
}

// -- v3.2 semantic layer / retrieval-by-default / hierarchy knobs -------------
void syfox_engine_set_semantics(SyFoxHandle* h, int on) {
    if (h) h->eng->substrate().set_semantics(on != 0);
}

void syfox_engine_set_retrieval(SyFoxHandle* h, int on) {
    if (h) h->eng->set_retrieval(on != 0);
}

void syfox_engine_set_retrieval_topk(SyFoxHandle* h, int topk) {
    if (h) h->eng->set_retrieval_topk(topk);
}

void syfox_engine_set_retrieval_dose(SyFoxHandle* h, double dose) {
    if (h) h->eng->set_retrieval_dose(static_cast<float>(dose));
}

void syfox_engine_set_hierarchy(SyFoxHandle* h, int on) {
    if (h) h->eng->set_hierarchy(on != 0);
}

// -- script detection (v2.2 --lang auto) ------------------------------------
// Returns a malloc'd slug ("latin", "bengali", ...) or "unknown".
char* syfox_detect_script(const char* text) {
    if (!text) return nullptr;
    const si::script::Script sc = si::script::detect_script(text);
    const std::string slug = (sc == si::script::Script::Unknown)
        ? "unknown" : si::script::slug(sc);
    char* buf = static_cast<char*>(std::malloc(slug.size() + 1));
    std::memcpy(buf, slug.c_str(), slug.size() + 1);
    return buf;
}

// -- decide ------------------------------------------------------------------
// Shared marshaling for syfox_decide (no evidence) and syfox_decide_ex.
static std::string decide_json(SyFoxHandle* h, const char* state,
                               const char* questions_json, bool want_evidence) {
    std::string out;
    try {
        sfx::JV q = sfx::JV::parse(questions_json);
        if (!q.is_obj()) { return "{\"error\":\"questions must be an object\"}"; }
        syfox::Usage u;
        auto answers = h->eng->decide(state, q, u);
        u.calibrated = h->eng->calibration().fitted;   // decide() resets Usage
        sfx::JVObj ans;
        for (const auto& a : answers) {
            sfx::JVObj o;
            o["type"] = sfx::JV(a.type);
            if (a.type == "choice") {
                o["choice"] = sfx::JV(a.choice);
                sfx::JVObj pr;
                for (const auto& p : a.probabilities)
                    pr[p.first] = sfx::JV(std::round(p.second * 1000.0f) / 1000.0f);
                o["probabilities"] = sfx::JV(pr);
            } else if (a.type == "score") {
                o["score"] = sfx::JV(std::round(a.value * 1000.0f) / 1000.0f);
                sfx::JVObj pr;
                for (const auto& p : a.probabilities)
                    pr[p.first] = sfx::JV(std::round(p.second * 1000.0f) / 1000.0f);
                o["probabilities"] = sfx::JV(pr);
            } else if (a.type == "noul") {
                o["noul"] = sfx::JV(std::round(a.probability * 1000.0f) / 1000.0f);
            }
            o["confidence"] = sfx::JV(std::round(a.confidence * 1000.0f) / 1000.0f);
            o["deferred"] = sfx::JV(a.deferred);
            if (!a.reason.empty()) o["reason"] = sfx::JV(a.reason);
            ans[a.qid] = sfx::JV(o);
        }
        sfx::JVObj usage{
            {"state_tokens", static_cast<double>(u.state_tokens)},
            {"vocabulary", static_cast<double>(u.vocabulary)},
            {"lanes", static_cast<double>(u.lanes)},
            {"settled_energy", std::round(u.settled_energy * 1000.0f) / 1000.0f},
            {"calibrated", sfx::JV(u.calibrated)},
            {"engine", std::string("syfox-") + syfox::VERSION},
            {"core", "si-substrate"}};
        if (!u.retrieved.empty()) {
            sfx::JVArr ret;
            for (const auto& r : u.retrieved)
                ret.push_back(sfx::JV(sfx::JVObj{
                    {"label", sfx::JV(r.first)},
                    {"resonance", std::round(r.second * 1000.0f) / 1000.0f}}));
            usage["retrieval"] = sfx::JV(ret);        // v3.2: priming memories
        }
        sfx::JVObj root{{"answers", sfx::JV(ans)}, {"usage", sfx::JV(usage)}};
        if (want_evidence)   // M3: supporting lanes + provenance + contradictions
            root["evidence"] = h->eng->evidence_json(state, q, answers);
        out = sfx::JV(root).dump();
    } catch (const std::exception& e) {
        out = std::string("{\"error\":\"") + e.what() + "\"}";
    }
    return out;
}

// Decides and returns a malloc'd JSON string: {"answers":..., "usage":...}.
// Caller frees with syfox_string_free. On malformed questions returns
// {"error": "..."}.
char* syfox_decide(SyFoxHandle* h, const char* state, const char* questions_json) {
    if (!h || !state || !questions_json) return nullptr;
    const std::string s = decide_json(h, state, questions_json, false);
    char* buf = static_cast<char*>(std::malloc(s.size() + 1));
    std::memcpy(buf, s.c_str(), s.size() + 1);
    return buf;
}

// opts_json: {"evidence":bool, "energy_norm":bool, "salience_gating":bool,
//             "miller_window":bool, "ngrams":"on"|"off",
//             "semantics":bool, "retrieval":bool, "retrieval_topk":int,
//             "retrieval_dose":num, "hierarchy":bool}
// Absent keys leave the current engine state untouched (sticky, CLI-equal).
char* syfox_decide_ex(SyFoxHandle* h, const char* state, const char* questions_json,
                      const char* opts_json) {
    if (!h || !state || !questions_json) return nullptr;
    bool evidence = false;
    if (opts_json && *opts_json) {
        try {
            sfx::JV o = sfx::JV::parse(opts_json);
            if (o.is_obj()) {
                if (o.has("evidence"))        evidence = truthy(o.at("evidence"));
                if (o.has("energy_norm"))     h->eng->set_energy_norm(truthy(o.at("energy_norm")));
                if (o.has("salience_gating") || o.has("miller_window"))
                    h->eng->substrate().set_source_modes(
                        o.has("salience_gating") && truthy(o.at("salience_gating")),
                        o.has("miller_window") && truthy(o.at("miller_window")));
                if (o.has("ngrams")) {
                    const std::string g = o.at("ngrams").as_str();
                    if (g == "on")  si::norm::grams_enabled() = true;
                    if (g == "off") si::norm::grams_enabled() = false;
                }
                if (o.has("semantics"))       h->eng->substrate().set_semantics(truthy(o.at("semantics")));
                if (o.has("retrieval"))       h->eng->set_retrieval(truthy(o.at("retrieval")));
                if (o.has("retrieval_topk"))  h->eng->set_retrieval_topk(static_cast<int>(o.at("retrieval_topk").as_num(5)));
                if (o.has("retrieval_dose"))  h->eng->set_retrieval_dose(static_cast<float>(o.at("retrieval_dose").as_num(0.30)));
                if (o.has("hierarchy"))       h->eng->set_hierarchy(truthy(o.at("hierarchy")));
            }
        } catch (...) { /* opts are optional; a bad opts object is ignored */ }
    }
    const std::string s = decide_json(h, state, questions_json, evidence);
    char* buf = static_cast<char*>(std::malloc(s.size() + 1));
    std::memcpy(buf, s.c_str(), s.size() + 1);
    return buf;
}

// Model fabric summary: {"nodes":...,"lanes":...,"evidence_records":...,
// "calibrated":...,"version":...,"core":...}
char* syfox_engine_info(SyFoxHandle* h) {
    if (!h) return nullptr;
    const sfx::JVObj o{
        {"nodes", static_cast<double>(h->eng->substrate().node_count())},
        {"lanes", static_cast<double>(h->eng->substrate().lane_count())},
        {"evidence_records", static_cast<double>(h->eng->substrate().evidence_count())},
        {"calibrated", sfx::JV(h->eng->calibration().fitted)},
        {"semantics", sfx::JV(h->eng->substrate().has_semantics())},
        {"sem_edges", static_cast<double>(h->eng->substrate().resonance_edge_count())},
        {"lane_contexts", static_cast<double>(h->eng->substrate().lane_context_count())},
        {"retrieval_memories", static_cast<double>(h->eng->memories().size())},
        {"hierarchy", sfx::JV(h->eng->hierarchy_on())},
        {"version", std::string(syfox::VERSION)},
        {"core", "si-substrate"}};
    const std::string s = sfx::JV(o).dump();
    char* buf = static_cast<char*>(std::malloc(s.size() + 1));
    std::memcpy(buf, s.c_str(), s.size() + 1);
    return buf;
}

void syfox_string_free(char* s) { std::free(s); }

} // extern "C"