File size: 10,135 Bytes
84b52f0
 
 
 
 
 
 
a3f913d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
84b52f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3f913d
84b52f0
 
 
 
 
 
 
 
 
 
 
 
 
a3f913d
 
84b52f0
a3f913d
84b52f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3f913d
 
 
84b52f0
 
 
 
 
 
 
 
 
 
 
a3f913d
 
84b52f0
 
 
a3f913d
84b52f0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a3f913d
84b52f0
 
 
 
 
 
 
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
#include "cognition/llama_bridge.h"
#include <llama.h>
#include <vector>
#include <stdexcept>

namespace tbv {

// Run 2 Plan §B4 — truncated fragments (mid-sentence cuts from the token cap) were
// entering memory stores and getting re-mutated forever; visible to every real visitor.
// Finds the LAST sentence terminator and truncates just after it (absorbing one
// immediately-following closing quote/paren, since a terminator often hides behind
// one -- e.g. `he said "stop."` ). If no terminator exists at all, the text is left
// untouched -- never return an empty string just because generation never finished
// a sentence within budget.
static void trim_to_sentence(std::string& s) {
    static const std::string closers = "\"')"; // ASCII closers only -- avoid UTF-8 byte-match foot-guns
    for (size_t i = s.size(); i-- > 0; ) {
        char c = s[i];
        if (c == '.' || c == '!' || c == '?') {
            size_t end = i + 1;
            if (end < s.size() && closers.find(s[end]) != std::string::npos) end++;
            if (end < s.size()) s.erase(end);
            return;
        }
    }
    // no terminator found -- keep raw, don't manufacture an empty string
}

LlamaBridge::LlamaBridge(const std::string& model_path) {
    llama_backend_init();
    llama_model_params mparams = llama_model_default_params();
    model = llama_model_load_from_file(model_path.c_str(), mparams);
    if (!model) throw std::runtime_error("Failed to load llama model");
    vocab = llama_model_get_vocab(model);

    llama_context_params cparams = llama_context_default_params();
    cparams.n_ctx = 2048;          // re-sized: ~160-tok thought + intent fits; revisit for concurrency
    cparams.n_threads = 1;
    cparams.n_threads_batch = 1;
    ctx = llama_init_from_model(model, cparams);
    if (!ctx) throw std::runtime_error("Failed to create llama context");
}

LlamaBridge::~LlamaBridge() {
    if (ctx) llama_free(ctx);
    if (model) llama_model_free(model);
    llama_backend_free();
}

// Decode a raw string into the active context (appends, no KV reset).
static void decode_str(llama_context* ctx, const llama_vocab* vocab, llama_batch& b,
                       int32_t& pos, const std::string& s, bool add_bos) {
    std::vector<llama_token> t(s.size() + 16);
    int n = llama_tokenize(vocab, s.c_str(), s.length(), t.data(), t.size(), add_bos, true);
    if (n < 0) { t.resize(-n); n = llama_tokenize(vocab, s.c_str(), s.length(), t.data(), t.size(), add_bos, true); }
    t.resize(n);
    b.n_tokens = 0;
    for (int i = 0; i < n; ++i) {
        b.token[b.n_tokens] = t[i]; b.pos[b.n_tokens] = pos++;
        b.n_seq_id[b.n_tokens] = 1; b.seq_id[b.n_tokens][0] = 0;
        b.logits[b.n_tokens] = (i == n - 1); b.n_tokens++;
    }
    llama_decode(ctx, b);
}

// Free (un-grammared) generation segment. Returns text.
static std::string gen(llama_context* ctx, const llama_vocab* vocab, llama_batch& b,
                       int32_t& pos, llama_sampler* chain, int max_tokens) {
    std::string out; int n_vocab = llama_vocab_n_tokens(vocab);
    for (int i = 0; i < max_tokens; ++i) {
        float* lg = llama_get_logits_ith(ctx, b.n_tokens - 1);
        if (!lg) break;
        std::vector<llama_token_data> cd; cd.reserve(n_vocab);
        for (llama_token t = 0; t < n_vocab; ++t) cd.push_back(llama_token_data{t, lg[t], 0.0f});
        llama_token_data_array cp = { cd.data(), cd.size(), -1, false };
        llama_sampler_apply(chain, &cp);
        llama_token nt = (cp.selected != -1) ? cp.data[cp.selected].id : cp.data[0].id;
        llama_sampler_accept(chain, nt);
        if (llama_vocab_is_eog(vocab, nt)) break;
        char buf[128]; int nc = llama_token_to_piece(vocab, nt, buf, sizeof(buf), 0, true);
        if (nc > 0) out.append(buf, nc);
        b.n_tokens = 0; b.token[0] = nt; b.pos[0] = pos++;
        b.n_seq_id[0] = 1; b.seq_id[0][0] = 0; b.logits[0] = true; b.n_tokens = 1;
        if (llama_decode(ctx, b) != 0) break;
    }
    return out;
}

Cognition LlamaBridge::infer(VillagerID id, const PerceptionContext& pc, uint64_t seed) {
    (void)id;
    // ChatML-wrap the grounded situation.
    llama_chat_message msg[1]; msg[0].role = "user"; msg[0].content = pc.situation.c_str();
    const char* tmpl = llama_model_chat_template(model, nullptr);
    std::vector<char> tp(pc.situation.size() + 1024);
    int r = llama_chat_apply_template(tmpl, msg, 1, true, tp.data(), tp.size());
    if (r > (int)tp.size()) { tp.resize(r); r = llama_chat_apply_template(tmpl, msg, 1, true, tp.data(), tp.size()); }
    std::string prompt = (r > 0) ? std::string(tp.data()) : pc.situation;

    // Fresh context per call keeps determinism + isolates KV (Phase 1).
    llama_memory_t mem = llama_get_memory(ctx);
    llama_memory_seq_rm(mem, 0, -1, -1);

    llama_batch b = llama_batch_init(2048, 0, 1);
    int32_t pos = 0;
    decode_str(ctx, vocab, b, pos, prompt, true);

    llama_sampler_chain_params sp = llama_sampler_chain_default_params();
    llama_sampler* chain = llama_sampler_chain_init(sp);
    llama_sampler_chain_add(chain, llama_sampler_init_penalties(200, 1.15f, 0.0f, 0.0f));
    llama_sampler_chain_add(chain, llama_sampler_init_top_k(50));
    llama_sampler_chain_add(chain, llama_sampler_init_top_p(0.9, 1));
    llama_sampler_chain_add(chain, llama_sampler_init_temp(0.8));
    llama_sampler_chain_add(chain, llama_sampler_init_dist(seed));

    // Segment 1: free thought.
    std::string thought = gen(ctx, vocab, b, pos, chain, n_thought_cap);
    trim_to_sentence(thought); // §B4 -- thought only, never intent (resolver needs the raw text)
    // Free intent micro-gen (fresh user turn, same context).
    decode_str(ctx, vocab, b, pos,
        "<|im_end|>\n<|im_start|>user\nIn one short sentence, what do you physically do right now?<|im_end|>\n<|im_start|>assistant\nI ", false);
    std::string intent = gen(ctx, vocab, b, pos, chain, 28);

    llama_sampler_free(chain);
    llama_batch_free(b);
    llama_memory_seq_rm(mem, 0, -1, -1);

    Resolved res = resolve(intent, pc.needs, pc.env);
    return { thought, intent, res.verb, res.source };
}

RetellResult LlamaBridge::retell(VillagerID id, const std::string& heard,
                                  const std::string& persona, uint64_t seed) {
    (void)id;
    std::string user = persona + "You overhear someone in the village saying: \"" + heard +
        "\" In your own words, what do you make of it and pass along to others?";
    llama_chat_message msg[1]; msg[0].role = "user"; msg[0].content = user.c_str();
    const char* tmpl = llama_model_chat_template(model, nullptr);
    std::vector<char> tp(user.size() + 1024);
    int r = llama_chat_apply_template(tmpl, msg, 1, true, tp.data(), tp.size());
    if (r > (int)tp.size()) { tp.resize(r); r = llama_chat_apply_template(tmpl, msg, 1, true, tp.data(), tp.size()); }
    std::string prompt = (r > 0) ? std::string(tp.data()) : user;

    llama_memory_t mem = llama_get_memory(ctx);
    llama_memory_seq_rm(mem, 0, -1, -1);
    llama_batch b = llama_batch_init(2048, 0, 1);
    int32_t pos = 0;
    decode_str(ctx, vocab, b, pos, prompt, true);

    llama_sampler_chain_params sp = llama_sampler_chain_default_params();
    llama_sampler* chain = llama_sampler_chain_init(sp);
    llama_sampler_chain_add(chain, llama_sampler_init_penalties(200, 1.15f, 0.0f, 0.0f));
    llama_sampler_chain_add(chain, llama_sampler_init_top_k(50));
    llama_sampler_chain_add(chain, llama_sampler_init_top_p(0.9, 1));
    llama_sampler_chain_add(chain, llama_sampler_init_temp(0.8));
    llama_sampler_chain_add(chain, llama_sampler_init_dist(seed));

    std::string out = gen(ctx, vocab, b, pos, chain, 90);
    trim_to_sentence(out); // §B4 -- MUST happen before hearsay_hop's forced-novelty append
                           // (live evidence tick 31908: distortion glued onto a truncated
                           // sentence with no boundary between them)
    llama_sampler_free(chain);
    llama_batch_free(b);
    llama_memory_seq_rm(mem, 0, -1, -1);

    // Segment 2: structured extraction from the free reconstruction (deterministic).
    RetellResult rr;
    rr.out_text = out;
    rr.fields   = extract_hearsay_fields(out);
    return rr;
}

std::string LlamaBridge::dream(VillagerID id, const std::vector<std::string>& fragments,
                                const std::string& persona, uint64_t seed) {
    (void)id;
    std::string frag;
    for (size_t i = 0; i < fragments.size(); ++i) frag += "\n- \"" + fragments[i] + "\"";
    std::string user = persona + "You drift into sleep. Fragments of your memories swirl together:" + frag +
        "\nDescribe the strange, surreal dream that forms from them.";
    llama_chat_message msg[1]; msg[0].role = "user"; msg[0].content = user.c_str();
    const char* tmpl = llama_model_chat_template(model, nullptr);
    std::vector<char> tp(user.size() + 1024);
    int r = llama_chat_apply_template(tmpl, msg, 1, true, tp.data(), tp.size());
    if (r > (int)tp.size()) { tp.resize(r); r = llama_chat_apply_template(tmpl, msg, 1, true, tp.data(), tp.size()); }
    std::string prompt = (r > 0) ? std::string(tp.data()) : user;

    llama_memory_t mem = llama_get_memory(ctx);
    llama_memory_seq_rm(mem, 0, -1, -1);
    llama_batch b = llama_batch_init(2048, 0, 1);
    int32_t pos = 0;
    decode_str(ctx, vocab, b, pos, prompt, true);

    llama_sampler_chain_params sp = llama_sampler_chain_default_params();
    llama_sampler* chain = llama_sampler_chain_init(sp);
    llama_sampler_chain_add(chain, llama_sampler_init_penalties(200, 1.15f, 0.0f, 0.0f));
    llama_sampler_chain_add(chain, llama_sampler_init_top_k(50));
    llama_sampler_chain_add(chain, llama_sampler_init_top_p(0.9, 1));
    llama_sampler_chain_add(chain, llama_sampler_init_temp(0.9)); // hotter -> more surreal
    llama_sampler_chain_add(chain, llama_sampler_init_dist(seed));

    std::string out = gen(ctx, vocab, b, pos, chain, 100);
    trim_to_sentence(out); // §B4
    llama_sampler_free(chain);
    llama_batch_free(b);
    llama_memory_seq_rm(mem, 0, -1, -1);
    return out;
}

} // namespace tbv