char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

Xuan-Son Nguyencommon: add json.h abstraction (#27511)d9f918d2d

char/aliceai-t5-moe
18.0 KiB453 linesraw
1#pragma once
2
3#include "chat.h"
4#include "common.h"
5#include "jinja/caps.h"
6#include "peg-parser.h"
7#include "json.h"
8
9#include <chrono>
10#include <optional>
11#include <string>
12#include <utility>
13#include <vector>
14
15using json = common_json;
16
17class common_chat_peg_builder;
18
19// ============================================================================
20// Parameters for template application (low-level, used by diff analysis)
21// ============================================================================
22struct template_params {
23    json                messages;
24    json                tools;
25    bool                add_generation_prompt = false;
26    bool                enable_thinking       = true;
27    std::optional<json> extra_context         = std::nullopt;
28};
29
30struct diff_split {
31    std::string prefix;
32    std::string suffix;
33    std::string left;
34    std::string right;
35
36    bool operator==(struct diff_split & other) const {
37        return prefix == other.prefix && suffix == other.suffix && left == other.left && right == other.right;
38    }
39};
40
41// Result of compare_variants containing diff and original outputs
42struct compare_variants_result {
43    diff_split  diff;
44    std::string output_A;
45    std::string output_B;
46};
47
48namespace autoparser {
49
50// ============================================================================
51// High-level params for parser generation
52// ============================================================================
53
54struct generation_params {
55    json                                  messages;
56    json                                  tools;
57    common_chat_tool_choice               tool_choice = COMMON_CHAT_TOOL_CHOICE_AUTO;
58    json                                  json_schema;
59    bool                                  parallel_tool_calls = true;
60    common_reasoning_format               reasoning_format    = COMMON_REASONING_FORMAT_AUTO;
61    bool                                  stream              = true;
62    std::string                           grammar;
63    bool                                  add_generation_prompt  = false;
64    common_chat_continuation              continue_final_message = COMMON_CHAT_CONTINUATION_NONE;
65    common_chat_msg                       continue_msg;
66    bool                                  enable_thinking        = true;
67    std::chrono::system_clock::time_point now                    = std::chrono::system_clock::now();
68    json                                  extra_context;
69    bool                                  add_bos       = false;
70    bool                                  add_eos       = false;
71    bool                                  is_inference  = true;
72    bool                                  add_inference = false;
73    bool                                  mark_input    = true;  // whether to mark input strings in the jinja context
74
75    bool has_continuation() const {
76        return continue_final_message != COMMON_CHAT_CONTINUATION_NONE && !continue_msg.empty();
77    }
78};
79
80// ============================================================================
81// Analysis Result Enums
82// ============================================================================
83
84// Reasoning handling mode (derived from R1-R3 comparisons)
85enum class reasoning_mode {
86    NONE,           // No reasoning markers detected
87    TAG_BASED,      // Tag-based: <think>...</think> (start can be empty for delimiter-style)
88    TOOLS_ONLY      // Only reason on tool calls, not on normal content
89};
90
91inline std::ostream & operator<<(std::ostream & os, const reasoning_mode & mode) {
92    switch (mode) {
93        case reasoning_mode::NONE:
94            return os << "NONE";
95        case reasoning_mode::TAG_BASED:
96            return os << "TAG_BASED";
97        case reasoning_mode::TOOLS_ONLY:
98            return os << "TOOLS_ONLY";
99        default:
100            return os << "UNKNOWN";
101    }
102}
103
104// Content wrapping mode (derived from C1 comparison)
105enum class content_mode {
106    PLAIN,                   // No content markers
107    ALWAYS_WRAPPED,          // Content always wrapped with markers
108    WRAPPED_WITH_REASONING,  // Content wrapped only when reasoning present
109};
110
111inline std::ostream & operator<<(std::ostream & os, const content_mode & mode) {
112    switch (mode) {
113        case content_mode::PLAIN:
114            return os << "PLAIN";
115        case content_mode::ALWAYS_WRAPPED:
116            return os << "ALWAYS_WRAPPED";
117        case content_mode::WRAPPED_WITH_REASONING:
118            return os << "WRAPPED_WITH_REASONING";
119        default:
120            return os << "UNKNOWN";
121    }
122}
123
124// Call ID position in tool calls (for non-JSON formats)
125enum class call_id_position {
126    NONE,                   // No call ID support detected
127    PRE_FUNC_NAME,          // Call ID before function name: [CALL_ID]id[FUNC]name{args}
128    BETWEEN_FUNC_AND_ARGS,  // Call ID between function and args: [FUNC]name[CALL_ID]id{args}
129    POST_ARGS,              // Call ID after arguments: [FUNC]name{args}[CALL_ID]id
130};
131
132inline std::ostream & operator<<(std::ostream & os, const call_id_position & pos) {
133    switch (pos) {
134        case call_id_position::NONE:
135            return os << "NONE";
136        case call_id_position::PRE_FUNC_NAME:
137            return os << "PRE_FUNC_NAME";
138        case call_id_position::BETWEEN_FUNC_AND_ARGS:
139            return os << "BETWEEN_FUNC_AND_ARGS";
140        case call_id_position::POST_ARGS:
141            return os << "POST_ARGS";
142        default:
143            return os << "UNKNOWN";
144    }
145}
146
147// Tool call format classification (derived from T1-T5, A1-A3 comparisons)
148enum class tool_format {
149    NONE,             // No tool support detected
150    JSON_NATIVE,      // Pure JSON: {"name": "X", "arguments": {...}}
151    TAG_WITH_JSON,    // Tag-based with JSON args: <function=X>{...}</function>
152    TAG_WITH_TAGGED,  // Tag-based with tagged args: <param=key>value</param>
153};
154
155inline std::ostream & operator<<(std::ostream & os, const tool_format & format) {
156    switch (format) {
157        case tool_format::NONE:
158            return os << "NONE";
159        case tool_format::JSON_NATIVE:
160            return os << "JSON_NATIVE";
161        case tool_format::TAG_WITH_JSON:
162            return os << "TAG_WITH_JSON";
163        case tool_format::TAG_WITH_TAGGED:
164            return os << "TAG_WITH_TAGGED";
165        default:
166            return os << "UNKNOWN";
167    }
168}
169
170// ============================================================================
171// Sub-structs for tool analysis
172// ============================================================================
173
174struct tool_format_analysis {
175    tool_format mode = tool_format::NONE;
176
177    std::string section_start;   // e.g., "<tool_call>", "[TOOL_CALLS]", ""
178    std::string section_end;     // e.g., "</tool_call>", ""
179    std::string per_call_start;  // e.g., "<|tool_call_begin|>", "" (for multi-call templates)
180    std::string per_call_end;    // e.g., "<|tool_call_end|>", ""
181
182    bool fun_name_is_key = false;       // In JSON format function name is JSON key, i.e. { "<funname>": { ... arguments ... } }
183    bool tools_array_wrapped = false;   // Tool calls wrapped in JSON array [...]
184    bool openai_wrapper_trigger = false;  // model emits the OpenAI function wrapper, trigger on it
185
186    std::string              function_field = "function";
187    std::string              name_field     = "name";
188    std::string              args_field     = "arguments";
189    std::string              id_field;
190    std::string              gen_id_field;
191    std::vector<std::string> parameter_order;
192};
193
194struct tool_function_analysis {
195    std::string name_prefix;     // e.g., "<function=", "\"name\": \"", "functions."
196    std::string name_suffix;     // e.g., ">", "\"", ":0"
197    std::string args_separator;  // e.g., "<tool_sep>" (marker between function name and arguments)
198    std::string close;           // e.g., "</function>", "" (for tag-based)
199};
200
201struct tool_arguments_analysis {
202    std::string start;          // e.g., "<|tool_call_argument_begin|>", "<args>"
203    std::string end;            // e.g., "<|tool_call_argument_end|>", "</args>"
204    std::string name_prefix;   // e.g., "<param=", "<arg_key>", "\""
205    std::string name_suffix;   // e.g., ">", "</arg_key>", "\":"
206    std::string value_prefix;  // e.g., "", "<arg_value>", ""
207    std::string value_suffix;  // e.g., "</param>", "</arg_value>", ""
208    std::string separator;     // e.g., "", "\n", ","
209    bool tolerate_intertag_whitespace = false; // Laguna: accept optional whitespace between arg tags
210};
211
212struct tool_id_analysis {
213    call_id_position pos = call_id_position::NONE;
214
215    std::string prefix;  // e.g., "[CALL_ID]" (marker before call ID value)
216    std::string suffix;  // e.g., "" (marker after call ID value, before next section)
217};
218
219// ============================================================================
220// Parser build context (shared interface for build_parser methods)
221// ============================================================================
222
223struct analyze_content;
224struct analyze_reasoning;
225
226struct parser_build_context {
227    common_chat_peg_builder & p;
228    const generation_params &         inputs;
229    common_peg_parser                 reasoning_parser;
230    bool                              extracting_reasoning = false;
231    const analyze_reasoning *         reasoning            = nullptr;
232    const analyze_content *           content              = nullptr;
233
234    parser_build_context(common_chat_peg_builder & p, const generation_params & inputs);
235};
236
237// ============================================================================
238// Base class for analyzers with parser building
239// ============================================================================
240
241struct analyze_base {
242    virtual ~analyze_base() = default;
243    virtual common_peg_parser build_parser(parser_build_context & ctx) const = 0;
244
245  protected:
246    const common_chat_template * tmpl = nullptr;
247
248    analyze_base() = default;
249    explicit analyze_base(const common_chat_template & tmpl) : tmpl(&tmpl) {}
250};
251
252// ============================================================================
253// Reasoning analyzer
254// ============================================================================
255
256struct analyze_reasoning : analyze_base {
257    reasoning_mode mode = reasoning_mode::NONE;
258
259    std::string start;  // e.g., "<think>", "[THINK]", "<|START_THINKING|>", ""
260    std::string end;    // e.g., "</think>", "[BEGIN FINAL RESPONSE]", "<|END_THINKING|>"
261
262    analyze_reasoning() = default;
263    analyze_reasoning(const common_chat_template & tmpl, bool supports_tools);
264    analyze_reasoning(std::string start_, std::string end_) : start(std::move(start_)), end(std::move(end_)) {}
265
266    common_peg_parser build_parser(parser_build_context & ctx) const override;
267
268  private:
269    // Look for reasoning markers in rendered content
270    void compare_reasoning_presence();
271
272    // Compare generation prompt with enable_thinking=true vs false
273    void compare_thinking_enabled();
274
275    // Check if reasoning is always possible or only in tool calls
276    void compare_reasoning_scope();
277};
278
279// ============================================================================
280// Content analyzer
281// ============================================================================
282
283struct analyze_content : analyze_base {
284    content_mode mode = content_mode::PLAIN;
285
286    std::string start;  // e.g., "<response>", ">>>all\n", ""
287    std::string end;    // e.g., "</response>", ""
288
289    bool requires_nonnull_content = false;
290
291    analyze_content() = default;
292    analyze_content(const common_chat_template & tmpl, const analyze_reasoning & reasoning);
293
294    common_peg_parser build_parser(parser_build_context & ctx) const override;
295
296    bool is_always_wrapped() const;
297    common_peg_parser build_optional_wrapped(parser_build_context & ctx) const;
298};
299
300// ============================================================================
301// Tool analyzer
302// ============================================================================
303
304struct analyze_tools : analyze_base {
305    tool_format_analysis    format;
306    tool_function_analysis  function;
307    tool_arguments_analysis arguments;
308    tool_id_analysis        call_id;
309
310    analyze_tools() = default;
311    analyze_tools(const common_chat_template & tmpl,
312                  const jinja::caps &          caps,
313                  const analyze_reasoning &    reasoning);
314
315    common_peg_parser build_parser(parser_build_context & ctx) const override;
316
317  private:
318    // Extract tool calling 'haystack' for further analysis and delegate further analysis based on format
319    void analyze_tool_calls(const analyze_reasoning & reasoning, bool supports_parallel_tool_calls);
320
321    // Analyze format based on position of function and argument name in needle
322    void analyze_tool_call_format(const std::string &       haystack,
323                                  const std::string &       fun_name_needle,
324                                  const std::string &       arg_name_needle,
325                                  const analyze_reasoning & reasoning,
326                                  bool                      supports_parallel_tool_calls);
327
328    // Analyze specifics of JSON native format (entire tool call is a JSON object)
329    void analyze_tool_call_format_json_native(const std::string & clean_haystack,
330                                              const std::string & fun_name_needle,
331                                              const std::string & arg_name_needle);
332
333    // Check if parallel calls in JSON native format array wrapped or tag wrapped
334    void analyze_json_native_parallel_calls();
335
336    // Analyze specifics of non-JSON native format (tags for function name or for function name and arguments)
337    void analyze_tool_call_format_non_json(const std::string & clean_haystack,
338                                           const std::string & fun_name_needle);
339
340    // Check for and extract specific per-call markers for non-native-JSON templates with parallel call support
341    void check_per_call_markers();
342
343    // Extract function name markers
344    void extract_function_markers();
345
346    // Delegates to separate functions for: separator analysis, argument name analysis, argument value analysis
347    void analyze_arguments();
348
349    // Extract argument name markers
350    void extract_argument_name_markers();
351
352    // Extract argument value markers
353    void extract_argument_value_markers();
354
355    // Extract argument separator, if specified (eg. <arg=foo>...</arg><sep><arg=bar>...</arg>)
356    void extract_argument_separator();
357
358    // Extract argument wrapper markers, if present (eg. '<args><arg=foo>...</arg><arg=bar>...</arg></args>')
359    void extract_args_markers();
360
361    // Extract call ID markers, if present
362    void extract_call_id_markers();
363
364    // Per-format tool parser builders
365    common_peg_parser build_tool_parser_json_native(parser_build_context & ctx) const;
366    common_peg_parser build_tool_parser_tag_json(parser_build_context & ctx) const;
367    common_peg_parser build_tool_parser_tag_tagged(parser_build_context & ctx) const;
368
369    // Shared helper: builds func_parser from open+call_id+args, handling atomic wrapping and close.
370    // atomic_peek: if present, used as the peek expression in the third atomicity branch.
371    common_peg_parser build_func_parser(common_chat_peg_builder & p, const std::string & name,
372                                        const common_peg_parser & call_id_section, bool have_call_id,
373                                        const common_peg_parser & args,
374                                        std::optional<common_peg_parser> atomic_peek) const;
375};
376
377// ============================================================================
378// Main autoparser class
379// ============================================================================
380
381struct autoparser {
382    jinja::caps          jinja_caps;
383    std::string          user_start;
384    std::string          assistant_start;
385    analyze_reasoning    reasoning;
386    analyze_content      content;
387    analyze_tools        tools;
388    bool                 analysis_complete = false;
389
390    // Preserved tokens for tokenizer (union of all non-empty markers)
391    std::vector<std::string> preserved_tokens;
392    std::vector<std::string> additional_stops;  // literal stop strings (e.g. Laguna </assistant>) caught however tokenized
393
394    autoparser() = default;
395
396    // Find the starting marker for the user message and assistant message
397    std::string detect_user_start_marker(const common_chat_template & tmpl);
398    std::string detect_assistant_start_marker(const common_chat_template & tmpl);
399
400    // Run full differential analysis on a template
401    void analyze_template(const common_chat_template & tmpl);
402
403    // Build the PEG parser for this template
404    common_peg_arena build_parser(const generation_params & inputs, const std::string & generation_prompt) const;
405
406  private:
407    // Collect tokens from entire analysis to preserve
408    void collect_preserved_tokens();
409};
410
411// ============================================================================
412// Parser generator
413// ============================================================================
414
415class peg_generator {
416  public:
417    static common_chat_params generate_parser(const common_chat_template &    tmpl,
418                                              const struct generation_params & inputs);
419
420    static common_chat_params generate_parser(const common_chat_template &    tmpl,
421                                              const struct generation_params & inputs,
422                                              const autoparser &              autoparser);
423};
424
425}  // namespace autoparser
426
427enum segment_type { TEXT, MARKER };
428
429inline std::ostream & operator<<(std::ostream & os, const segment_type & type) {
430    switch (type) {
431        case segment_type::TEXT:
432            return os << "TEXT";
433        case segment_type::MARKER:
434            return os << "MARKER";
435        default:
436            return os << "UNKNOWN";
437    }
438}
439
440struct segment {
441    segment_type type;
442    std::string  value;
443
444    segment(segment_type type, std::string value) : type(type), value(std::move(value)) {}
445
446    bool operator==(const segment & other) const {
447        return type == other.type && value == other.value;
448    }
449
450    bool operator!=(const segment & other) const {
451        return !(*this == other);
452    }
453};