jbrashear commited on
Commit
5b02a85
·
verified ·
1 Parent(s): 5aaf5ba

Quantized weights, tokenizer, temperatures, scripts

Browse files
.gitattributes CHANGED
@@ -33,3 +33,7 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ jebadiah-27b-Q4_K_M.gguf filter=lfs diff=lfs merge=lfs -text
37
+ jebadiah-27b-Q5_K_M.gguf filter=lfs diff=lfs merge=lfs -text
38
+ jebadiah-27b-Q8_0.gguf filter=lfs diff=lfs merge=lfs -text
39
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
LICENSE ADDED
@@ -0,0 +1,202 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright 2026 Alibaba Cloud
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
chat_template.jinja ADDED
@@ -0,0 +1,170 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- set image_count = namespace(value=0) %}
2
+ {%- set video_count = namespace(value=0) %}
3
+ {%- macro render_content(content, do_vision_count, is_system_content=false) %}
4
+ {%- if content is string %}
5
+ {{- content }}
6
+ {%- elif content is iterable and content is not mapping %}
7
+ {%- for item in content %}
8
+ {%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
9
+ {%- if is_system_content %}
10
+ {{- raise_exception('System message cannot contain images.') }}
11
+ {%- endif %}
12
+ {%- if do_vision_count %}
13
+ {%- set image_count.value = image_count.value + 1 %}
14
+ {%- endif %}
15
+ {%- if add_vision_id %}
16
+ {{- 'Picture ' ~ image_count.value ~ ': ' }}
17
+ {%- endif %}
18
+ {{- '<|vision_start|><|image_pad|><|vision_end|>' }}
19
+ {%- elif 'video' in item or item.type == 'video' %}
20
+ {%- if is_system_content %}
21
+ {{- raise_exception('System message cannot contain videos.') }}
22
+ {%- endif %}
23
+ {%- if do_vision_count %}
24
+ {%- set video_count.value = video_count.value + 1 %}
25
+ {%- endif %}
26
+ {%- if add_vision_id %}
27
+ {{- 'Video ' ~ video_count.value ~ ': ' }}
28
+ {%- endif %}
29
+ {{- '<|vision_start|><|video_pad|><|vision_end|>' }}
30
+ {%- elif 'text' in item %}
31
+ {{- item.text }}
32
+ {%- else %}
33
+ {{- raise_exception('Unexpected item type in content.') }}
34
+ {%- endif %}
35
+ {%- endfor %}
36
+ {%- elif content is none or content is undefined %}
37
+ {{- '' }}
38
+ {%- else %}
39
+ {{- raise_exception('Unexpected content type.') }}
40
+ {%- endif %}
41
+ {%- endmacro %}
42
+ {%- if not messages %}
43
+ {{- raise_exception('No messages provided.') }}
44
+ {%- endif %}
45
+ {%- set reasoning_instructions = '' %}
46
+ {%- if enable_thinking is undefined or enable_thinking is true %}
47
+ {%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') %}
48
+ {%- if resolved_reasoning_effort not in ('xhigh', 'medium', 'low') %}
49
+ {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~ '. Supported types are xhigh (default), medium, and low.') }}
50
+ {%- endif %}
51
+ {%- if resolved_reasoning_effort == 'xhigh' %}
52
+ {%- set reasoning_instructions = 'Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer.' %}
53
+ {%- elif resolved_reasoning_effort == 'low' %}
54
+ {%- set reasoning_instructions = 'Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the conclusion without unnecessary elaboration.' %}
55
+ {%- endif %}
56
+ {%- endif %}
57
+ {%- if tools and tools is iterable and tools is not mapping %}
58
+ {{- '<|im_start|>system\n' }}
59
+ {%- if reasoning_instructions %}
60
+ {{- reasoning_instructions + '\n\n' }}
61
+ {%- endif %}
62
+ {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
63
+ {%- for tool in tools %}
64
+ {{- "\n" }}
65
+ {{- tool | tojson }}
66
+ {%- endfor %}
67
+ {{- "\n</tools>" }}
68
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
69
+ {%- if messages[0].role == 'system' %}
70
+ {%- set content = render_content(messages[0].content, false, true)|trim %}
71
+ {%- if content %}
72
+ {{- '\n\n' + content }}
73
+ {%- endif %}
74
+ {%- endif %}
75
+ {{- '<|im_end|>\n' }}
76
+ {%- else %}
77
+ {%- if messages[0].role == 'system' %}
78
+ {%- set content = render_content(messages[0].content, false, true)|trim %}
79
+ {%- if content %}
80
+ {{- '<|im_start|>system\n' + (reasoning_instructions + '\n\n' if reasoning_instructions else '') + content + '<|im_end|>\n' }}
81
+ {%- elif reasoning_instructions %}
82
+ {{- '<|im_start|>system\n' + reasoning_instructions + '<|im_end|>\n' }}
83
+ {%- endif %}
84
+ {%- elif reasoning_instructions %}
85
+ {{- '<|im_start|>system\n' + reasoning_instructions + '<|im_end|>\n' }}
86
+ {%- endif %}
87
+ {%- endif %}
88
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
89
+ {%- for message in messages[::-1] %}
90
+ {%- set index = (messages|length - 1) - loop.index0 %}
91
+ {%- if ns.multi_step_tool and message.role == "user" %}
92
+ {%- set content = render_content(message.content, false)|trim %}
93
+ {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
94
+ {%- set ns.multi_step_tool = false %}
95
+ {%- set ns.last_query_index = index %}
96
+ {%- endif %}
97
+ {%- endif %}
98
+ {%- endfor %}
99
+ {%- if ns.multi_step_tool %}
100
+ {{- raise_exception('No user query found in messages.') }}
101
+ {%- endif %}
102
+ {%- for message in messages %}
103
+ {%- set content = render_content(message.content, true)|trim %}
104
+ {%- if message.role == "system" %}
105
+ {%- if not loop.first %}
106
+ {{- raise_exception('System message must be at the beginning.') }}
107
+ {%- endif %}
108
+ {%- elif message.role == "user" %}
109
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
110
+ {%- elif message.role == "assistant" %}
111
+ {%- set reasoning_content = '' %}
112
+ {%- if message.reasoning_content is string %}
113
+ {%- set reasoning_content = message.reasoning_content %}
114
+ {%- endif %}
115
+ {%- set reasoning_content = reasoning_content|trim %}
116
+ {%- if preserve_thinking is undefined or preserve_thinking is true or loop.index0 > ns.last_query_index %}
117
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
118
+ {%- else %}
119
+ {{- '<|im_start|>' + message.role + '\n' + content }}
120
+ {%- endif %}
121
+ {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
122
+ {%- for tool_call in message.tool_calls %}
123
+ {%- if tool_call.function is defined %}
124
+ {%- set tool_call = tool_call.function %}
125
+ {%- endif %}
126
+ {%- if loop.first %}
127
+ {%- if content|trim %}
128
+ {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
129
+ {%- else %}
130
+ {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
131
+ {%- endif %}
132
+ {%- else %}
133
+ {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
134
+ {%- endif %}
135
+ {%- if tool_call.arguments is defined and tool_call.arguments != '' %}
136
+ {%- for args_name, args_value in tool_call.arguments|items %}
137
+ {{- '<parameter=' + args_name + '>\n' }}
138
+ {%- set args_value = args_value | string if args_value is string else args_value | tojson | safe %}
139
+ {{- args_value }}
140
+ {{- '\n</parameter>\n' }}
141
+ {%- endfor %}
142
+ {%- endif %}
143
+ {{- '</function>\n</tool_call>' }}
144
+ {%- endfor %}
145
+ {%- endif %}
146
+ {{- '<|im_end|>\n' }}
147
+ {%- elif message.role == "tool" %}
148
+ {%- if loop.previtem and loop.previtem.role != "tool" %}
149
+ {{- '<|im_start|>user' }}
150
+ {%- endif %}
151
+ {{- '\n<tool_response>\n' }}
152
+ {{- content }}
153
+ {{- '\n</tool_response>' }}
154
+ {%- if not loop.last and loop.nextitem.role != "tool" %}
155
+ {{- '<|im_end|>\n' }}
156
+ {%- elif loop.last %}
157
+ {{- '<|im_end|>\n' }}
158
+ {%- endif %}
159
+ {%- else %}
160
+ {{- raise_exception('Unexpected message role.') }}
161
+ {%- endif %}
162
+ {%- endfor %}
163
+ {%- if add_generation_prompt %}
164
+ {{- '<|im_start|>assistant\n' }}
165
+ {%- if enable_thinking is defined and enable_thinking is false %}
166
+ {{- '<think>\n\n</think>\n\n' }}
167
+ {%- else %}
168
+ {{- '<think>\n' }}
169
+ {%- endif %}
170
+ {%- endif %}
eval/agreement-Q4_K_M.json ADDED
The diff for this file is too large to render. See raw diff
 
eval/agreement-Q5_K_M.json ADDED
The diff for this file is too large to render. See raw diff
 
eval/agreement-Q8_0.json ADDED
The diff for this file is too large to render. See raw diff
 
jebadiah-27b-Q4_K_M.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:91b4c7ab57ce90a57f9cafeb447cde0b68d2c68e874a065616319676d29ea672
3
+ size 16810714560
jebadiah-27b-Q5_K_M.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bafef18a0757c05fceea401e3ae1e68996345fbfa2bc41310dd2b3187d118eb1
3
+ size 19535701440
jebadiah-27b-Q8_0.gguf ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:4b91f3142e982ffd7408fd4a2267f498a7ba5fe3d4206ad6382ad96385f911fe
3
+ size 29047084480
merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
scripts/ainode_prompt_verbatim.py ADDED
@@ -0,0 +1,294 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """AINode's prompt renderer, copied VERBATIM from the repo so training and serving render the
2
+ same bytes. Do not edit by hand: regenerate with train/make_verbatim.py.
3
+
4
+ source commit: e5c089386e0239c9eb270eeb490d181722b8da5b
5
+ files: ainode/api/decide.py (SYSTEM_PROMPT, ANSWER_INSTRUCTION, MAX_OPTIONS, TOP_LOGPROBS, BOOLEAN_OPTIONS, DecideError, option_label, option_labels, serialize_state, build_messages)
6
+ ainode/api/systemone.py (CHOICE, NOUL, SCORE, QUESTION_TYPES, NOUL_OPTIONS, MIN_SCORE_LEVELS, MAX_SCORE_LEVELS, MAX_CRITERIA, Translated, option_text, criteria_pairs, choice_options, noul_options, score_options, translate_one, translate_questions)
7
+ prompt_source_sha256: d2660ebec28bd3f1704235bda88d24a397c1c62475e740519cb8ef2d08f25fdd (sha256 of the copied definitions, in this order)
8
+
9
+ The served path is: systemone.translate_questions -> decide.normalize_questions (shape checks only)
10
+ -> decide.build_messages(serialize_state(state), None, question, options) -> the model's chat
11
+ template with add_generation_prompt=True and enable_thinking=False -> one label token.
12
+ """
13
+ from __future__ import annotations
14
+
15
+ import json
16
+ from typing import Any, NamedTuple, Optional
17
+
18
+ PROMPT_SOURCE_COMMIT = "e5c089386e0239c9eb270eeb490d181722b8da5b"
19
+ PROMPT_SOURCE_SHA256 = "d2660ebec28bd3f1704235bda88d24a397c1c62475e740519cb8ef2d08f25fdd"
20
+
21
+ SYSTEM_PROMPT = ("You are a decision function. Answer with the single letter "
22
+ "of the best option and nothing else.")
23
+
24
+
25
+ ANSWER_INSTRUCTION = "Answer with the label of one option and nothing else."
26
+
27
+
28
+ MAX_OPTIONS = 255
29
+
30
+
31
+ TOP_LOGPROBS = 20
32
+
33
+
34
+ BOOLEAN_OPTIONS = ("yes", "no")
35
+
36
+
37
+ class DecideError(Exception):
38
+ """A bad request shape. Carries the message the caller gets in the 4xx.
39
+
40
+ ``/v1/decide`` answers it as a 400 and ``/v1/systemone`` as the 422 the Jev
41
+ format specifies, so the message says what is wrong and never which status
42
+ somebody is about to put it in.
43
+ """
44
+
45
+
46
+ def option_label(index: int) -> str:
47
+ """Zero-based option index to its letter label: A..Z, AA, AB, ... IU.
48
+
49
+ Bijective base-26 (spreadsheet columns), so the scheme keeps going past Z
50
+ without a separator and without ever colliding.
51
+ """
52
+ if index < 0:
53
+ raise ValueError("option index cannot be negative")
54
+ n = index + 1
55
+ out = ""
56
+ while n > 0:
57
+ n, rem = divmod(n - 1, 26)
58
+ out = chr(ord("A") + rem) + out
59
+ return out
60
+
61
+
62
+ def option_labels(count: int) -> list[str]:
63
+ """The labels for a question with `count` options, in option order."""
64
+ return [option_label(i) for i in range(count)]
65
+
66
+
67
+ def serialize_state(state: Any) -> str:
68
+ """The state as the model sees it: a string verbatim, anything else compact JSON."""
69
+ if state is None:
70
+ return ""
71
+ if isinstance(state, str):
72
+ return state
73
+ try:
74
+ return json.dumps(state, separators=(",", ":"), sort_keys=True,
75
+ ensure_ascii=False)
76
+ except (TypeError, ValueError) as exc:
77
+ raise DecideError(f"'state' is not JSON-serializable: {exc}") from exc
78
+
79
+
80
+ def build_messages(state: str, instructions: Optional[str], question: str,
81
+ options: list[str]) -> list[dict]:
82
+ """The chat messages for one question. Pure, so the tests can pin the text.
83
+
84
+ The state comes BEFORE the question on purpose: every question in a request
85
+ then shares a byte-identical prefix (system message plus state), so the
86
+ engine's prefix cache prefills the shared part once no matter how many
87
+ questions are asked against it.
88
+ """
89
+ system = SYSTEM_PROMPT
90
+ extra = (instructions or "").strip()
91
+ if extra:
92
+ system = f"{SYSTEM_PROMPT}\n\n{extra}"
93
+ lines = ["STATE:", state, "", f"QUESTION: {question}", "", "OPTIONS:"]
94
+ lines += [f"{option_label(i)}. {opt}" for i, opt in enumerate(options)]
95
+ lines += ["", ANSWER_INSTRUCTION]
96
+ return [
97
+ {"role": "system", "content": system},
98
+ {"role": "user", "content": "\n".join(lines)},
99
+ ]
100
+
101
+
102
+ CHOICE = "choice"
103
+
104
+
105
+ NOUL = "noul"
106
+
107
+
108
+ SCORE = "score"
109
+
110
+
111
+ QUESTION_TYPES = (CHOICE, NOUL, SCORE)
112
+
113
+
114
+ NOUL_OPTIONS = ("true", "false")
115
+
116
+
117
+ MIN_SCORE_LEVELS = 2
118
+
119
+
120
+ MAX_SCORE_LEVELS = 10
121
+
122
+
123
+ MAX_CRITERIA = TOP_LOGPROBS
124
+
125
+
126
+ class Translated(NamedTuple):
127
+ """One Jev question as the decision core sees it, plus the way back out.
128
+
129
+ ``options`` is what the model reads, one line per option. ``names`` is what
130
+ each of those options answers to on the wire, in the same order: a choice
131
+ criteria key verbatim, ``true`` / ``false``, or a score level's position.
132
+ Keeping the pair here is what lets the answer name the caller's own key
133
+ rather than the letter the engine was constrained to.
134
+ """
135
+
136
+ kind: str
137
+ question: str
138
+ options: list[str]
139
+ names: list[str]
140
+
141
+
142
+ def option_text(name: str, description: Any) -> str:
143
+ """One option line: the name the answer will carry, then what it means.
144
+
145
+ The name comes first and VERBATIM because it is the string the caller's
146
+ client compares against, and a model that has read it beside its description
147
+ is choosing between meanings rather than between labels. The description is
148
+ flattened to one line, because the prompt renders one option per line and a
149
+ description with a newline in it would read as two options.
150
+ """
151
+ if isinstance(description, str) and description.strip():
152
+ return f"{name}: {' '.join(description.split())}"
153
+ return name
154
+
155
+
156
+ def criteria_pairs(key: str, criteria: Any, kind: str) -> list[tuple[str, Any]]:
157
+ """The ``(name, description)`` pairs of an object ``criteria``, in order.
158
+
159
+ Insertion order is the rubric order for a score, and JSON parsing preserves
160
+ it, so nothing here sorts. A name is validated stripped and kept as written:
161
+ the answer has to carry the caller's own key back, byte for byte, because the
162
+ caller's code looks that key up.
163
+ """
164
+ if not isinstance(criteria, dict) or not criteria:
165
+ raise DecideError(
166
+ f"question '{key}': a {kind} question needs a non-empty 'criteria' "
167
+ "object of {name: description}")
168
+ pairs: list[tuple[str, Any]] = []
169
+ for name, description in criteria.items():
170
+ if not isinstance(name, str) or not name.strip():
171
+ raise DecideError(f"question '{key}': every 'criteria' name must be a "
172
+ f"non-empty string (got {name!r})")
173
+ if description is not None and not isinstance(description, str):
174
+ raise DecideError(f"question '{key}': the 'criteria' description for "
175
+ f"'{name}' must be a string")
176
+ pairs.append((name.strip(), description))
177
+ return pairs
178
+
179
+
180
+ def choice_options(key: str, criteria: Any) -> tuple[list[str], list[str]]:
181
+ """A choice question's options and the criteria keys they answer to."""
182
+ pairs = criteria_pairs(key, criteria, "choice")
183
+ if len(pairs) < 2:
184
+ raise DecideError(f"question '{key}': a choice needs at least 2 'criteria' "
185
+ f"options, got {len(pairs)}")
186
+ if len(pairs) > MAX_CRITERIA:
187
+ raise DecideError(
188
+ f"question '{key}': {len(pairs)} 'criteria' options is more than the "
189
+ f"{MAX_CRITERIA} this node can report a probability for. One engine "
190
+ f"call carries back the top {TOP_LOGPROBS} labels, so a wider option "
191
+ "set would answer with a distribution missing its tail")
192
+ return ([option_text(name, desc) for name, desc in pairs],
193
+ [name for name, _ in pairs])
194
+
195
+
196
+ def noul_options(key: str, criteria: Any) -> tuple[list[str], list[str]]:
197
+ """A noul's two options, always ``true`` then ``false``.
198
+
199
+ The criteria block is optional here, and may describe one side only: some
200
+ clients send both, some send neither, and a yes-or-no question is still
201
+ answerable from its instructions alone. What a caller may not do is rename
202
+ the sides, because the answer is P(true) and nothing else can stand in for
203
+ it.
204
+ """
205
+ described: dict[str, Any] = {}
206
+ if criteria is not None:
207
+ if not isinstance(criteria, dict):
208
+ raise DecideError(f"question '{key}': 'criteria' must be an object of "
209
+ "{true: description, false: description}")
210
+ for name, description in criteria.items():
211
+ flat = name.strip() if isinstance(name, str) else name
212
+ if flat not in NOUL_OPTIONS:
213
+ raise DecideError(f"question '{key}': a noul's 'criteria' names only "
214
+ f"'true' and 'false' (got {name!r})")
215
+ if description is not None and not isinstance(description, str):
216
+ raise DecideError(f"question '{key}': the 'criteria' description for "
217
+ f"'{flat}' must be a string")
218
+ described[flat] = description
219
+ return ([option_text(name, described.get(name)) for name in NOUL_OPTIONS],
220
+ list(NOUL_OPTIONS))
221
+
222
+
223
+ def score_options(key: str, criteria: Any) -> tuple[list[str], list[str]]:
224
+ """A score's levels in rubric order: a list by position, an object by insertion.
225
+
226
+ Both spellings are accepted because both are in the wild: the list form names
227
+ the levels and nothing else, the object form names them and says what each
228
+ one means. Either way position 0 is the first level the caller wrote, which
229
+ is what the legend and the expected score are counted against.
230
+ """
231
+ if isinstance(criteria, list):
232
+ names: list[str] = []
233
+ for level in criteria:
234
+ if not isinstance(level, str) or not level.strip():
235
+ raise DecideError(f"question '{key}': every 'criteria' level must be "
236
+ f"a non-empty string (got {level!r})")
237
+ names.append(level.strip())
238
+ options = list(names)
239
+ elif isinstance(criteria, dict):
240
+ pairs = criteria_pairs(key, criteria, "score")
241
+ names = [name for name, _ in pairs]
242
+ options = [option_text(name, desc) for name, desc in pairs]
243
+ else:
244
+ raise DecideError(
245
+ f"question '{key}': a score question needs 'criteria', either an ordered "
246
+ "list of levels or an object of {level: description}")
247
+ if not MIN_SCORE_LEVELS <= len(names) <= MAX_SCORE_LEVELS:
248
+ raise DecideError(
249
+ f"question '{key}': a score's 'criteria' needs {MIN_SCORE_LEVELS} to "
250
+ f"{MAX_SCORE_LEVELS} ordered levels, got {len(names)}")
251
+ if len(set(names)) != len(names):
252
+ raise DecideError(f"question '{key}': 'criteria' repeats a level name. Every "
253
+ "level must be distinct so a score names one of them")
254
+ return options, names
255
+
256
+
257
+ def translate_one(key: str, spec: Any) -> Translated:
258
+ """One question off the wire. Reads three fields and ignores the rest.
259
+
260
+ ``type``, ``instructions`` and ``criteria`` are the whole question as far as
261
+ this route is concerned, which is also exactly what JDE's
262
+ ``questionsForWire`` sends. A field beyond them belongs to the caller's own
263
+ code, so it is neither read nor echoed.
264
+ """
265
+ if not isinstance(spec, dict):
266
+ raise DecideError(f"question '{key}' must be an object")
267
+ kind = spec.get("type")
268
+ if kind not in QUESTION_TYPES:
269
+ raise DecideError(f"question '{key}': 'type' must be one of "
270
+ f"{', '.join(QUESTION_TYPES)} (got {kind!r})")
271
+ instructions = spec.get("instructions")
272
+ if not isinstance(instructions, str) or not instructions.strip():
273
+ raise DecideError(f"question '{key}' needs a non-empty 'instructions' string")
274
+ criteria = spec.get("criteria")
275
+ if kind == NOUL:
276
+ options, names = noul_options(key, criteria)
277
+ elif kind == SCORE:
278
+ options, names = score_options(key, criteria)
279
+ else:
280
+ options, names = choice_options(key, criteria)
281
+ return Translated(kind, instructions.strip(), options, names)
282
+
283
+
284
+ def translate_questions(raw: Any) -> dict[str, Translated]:
285
+ """Every question in the body, in the order the caller wrote them."""
286
+ if not isinstance(raw, dict) or not raw:
287
+ raise DecideError("'questions' must be a non-empty object of {id: question}")
288
+ out: dict[str, Translated] = {}
289
+ for key, spec in raw.items():
290
+ if not isinstance(key, str) or not key.strip():
291
+ raise DecideError("every question id must be a non-empty string")
292
+ out[key] = translate_one(key, spec)
293
+ return out
294
+
scripts/decide_gguf.py ADDED
@@ -0,0 +1,96 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Run a Jebadiah GGUF through llama.cpp's llama-server and print typed answers.
2
+
3
+ The prompt is rendered in Python exactly as AINode's /v1/systemone does (jebadiah_prompt.py, the chat
4
+ template with thinking off) and the rendered text goes to llama-server's raw /completion endpoint, so the
5
+ server's own chat template never touches it. Nothing is generated: the answer is read off the log
6
+ probabilities of the single-token option labels ("A", "B", ...) at the answer position, renormalised over
7
+ those labels, with the model's per-type temperature from temperatures.json applied.
8
+
9
+ llama-server -m jebadiah-27b-Q4_K_M.gguf -c 4096 -np 1 --port 8080
10
+ python scripts/decide_gguf.py --server http://127.0.0.1:8080 --request scripts/example-request.json
11
+
12
+ --tokenizer is a folder (or Hub repo id) with tokenizer.json, tokenizer_config.json and chat_template.jinja;
13
+ this repository ships them, so the default is the folder above scripts/. Needs `transformers` (the tokenizer
14
+ only, no torch) and nothing else outside the standard library.
15
+ """
16
+ from __future__ import annotations
17
+
18
+ import argparse
19
+ import json
20
+ import math
21
+ import os
22
+ import sys
23
+ import urllib.request
24
+
25
+ HERE = os.path.dirname(os.path.abspath(__file__))
26
+ sys.path.insert(0, HERE)
27
+ from jebadiah_prompt import Renderer, answer_from_probs # noqa: E402
28
+
29
+
30
+ def load_renderer(tokenizer: str, max_tokens: int = 2048) -> Renderer:
31
+ from transformers import AutoTokenizer
32
+ tok = AutoTokenizer.from_pretrained(tokenizer)
33
+ if tok.pad_token_id is None:
34
+ tok.pad_token = tok.eos_token
35
+ return Renderer(tok, max_tokens)
36
+
37
+
38
+ def read_temperatures(path: str | None) -> dict:
39
+ if not path or not os.path.exists(path):
40
+ return {}
41
+ return {k: float(v) for k, v in json.load(open(path))["temperatures"].items()}
42
+
43
+
44
+ def post(server: str, path: str, body: dict, timeout: float = 900) -> dict:
45
+ req = urllib.request.Request(server.rstrip("/") + path, data=json.dumps(body).encode(),
46
+ headers={"Content-Type": "application/json"})
47
+ with urllib.request.urlopen(req, timeout=timeout) as r:
48
+ return json.loads(r.read())
49
+
50
+
51
+ def label_logprobs(server: str, prompt: str, cand_ids: list[int], n_probs: int = 1000) -> tuple[list[float], int]:
52
+ """Log probabilities (full-vocab softmax, before any sampling) of each candidate token at the position
53
+ right after `prompt`. A label outside the top n_probs gets the smallest returned value, an upper bound
54
+ that is already negligible after renormalisation. Returns (logprobs, number of labels not returned)."""
55
+ r = post(server, "/completion", {"prompt": prompt, "n_predict": 1, "n_probs": n_probs,
56
+ "post_sampling_probs": False, "cache_prompt": False,
57
+ "temperature": 0.0})
58
+ top = r["completion_probabilities"][0]["top_logprobs"]
59
+ lp = {t["id"]: t["logprob"] for t in top}
60
+ floor = min(lp.values())
61
+ return [lp.get(c, floor) for c in cand_ids], sum(1 for c in cand_ids if c not in lp)
62
+
63
+
64
+ def option_probs(logprobs: list[float], temperature: float = 1.0) -> list[float]:
65
+ """Softmax over the labels only, after dividing by the temperature. log p = logit - logsumexp(all
66
+ logits), and the constant cancels in the softmax, so this equals softmax(logits[labels] / T)."""
67
+ z = [x / temperature for x in logprobs]
68
+ m = max(z)
69
+ e = [math.exp(x - m) for x in z]
70
+ s = sum(e)
71
+ return [x / s for x in e]
72
+
73
+
74
+ def main():
75
+ ap = argparse.ArgumentParser()
76
+ ap.add_argument("--server", default="http://127.0.0.1:8080", help="a running llama-server with a Jebadiah GGUF")
77
+ ap.add_argument("--request", required=True, help="JSON file: {state, questions: {id: {type, instructions, criteria}}}")
78
+ ap.add_argument("--tokenizer", default=os.path.dirname(HERE), help="folder or Hub repo id with the tokenizer and chat template")
79
+ ap.add_argument("--temperatures", default=os.path.join(os.path.dirname(HERE), "temperatures.json"))
80
+ ap.add_argument("--no-temperatures", action="store_true", help="raw probabilities, as the served route returns today")
81
+ ap.add_argument("--n-probs", type=int, default=1000)
82
+ a = ap.parse_args()
83
+ req = json.load(open(a.request))
84
+ renderer = load_renderer(a.tokenizer)
85
+ temps = {} if a.no_temperatures else read_temperatures(a.temperatures)
86
+ out = {"temperatures_applied": temps, "answers": {}}
87
+ for qid, q in req["questions"].items():
88
+ rd = renderer.render(req["state"], q)
89
+ lps, _ = label_logprobs(a.server, rd.prompt, rd.cand_ids, a.n_probs)
90
+ probs = option_probs(lps, float(temps.get(q["type"], 1.0)))
91
+ out["answers"][qid] = answer_from_probs(q, rd.keys, probs)
92
+ print(json.dumps(out, indent=1))
93
+
94
+
95
+ if __name__ == "__main__":
96
+ main()
scripts/example-request.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {"state": {"ticket": "Customer says the invoice total does not match the quote."},
2
+ "questions": {
3
+ "route": {"type": "choice", "instructions": "Which team should take this ticket?", "criteria": {"billing": "an invoice, a charge or a refund", "support": "a product question", "sales": "a quote or a renewal"}},
4
+ "urgent": {"type": "noul", "instructions": "The customer is blocked from working.", "criteria": {"true": "work has stopped", "false": "it can wait"}}}}
scripts/jebadiah_prompt.py ADDED
@@ -0,0 +1,208 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """The Jebadiah prompt: AINode's own /v1/systemone -> /v1/decide rendering, then the base model's
2
+ chat template, then ONE label token. Train, eval and the local server all go through here, and
3
+ the served route (AINode) renders the same bytes because the renderer is AINode's, copied
4
+ verbatim (ainode_prompt_verbatim.py, hashed into the training config).
5
+
6
+ A question is the Jev wire shape {type, instructions, criteria}. AINode translates it to a list of
7
+ option lines with letter labels (A..Z, AA..) and the wire names each label answers to:
8
+ choice -> the criteria keys, in criteria order (or the order the caller passes)
9
+ noul -> ["true", "false"] (always this order, AINode's rule)
10
+ score -> the level texts in rubric order; we report them as level indices "0".."k-1"
11
+ The answer position is the token right after the generation prompt, and the candidate tokens are
12
+ the label strings themselves ("A", "B", ...), each one ordinary token at that boundary (checked
13
+ against the tokenizer at load time).
14
+ """
15
+ from __future__ import annotations
16
+
17
+ import json
18
+ from dataclasses import dataclass
19
+
20
+ from ainode_prompt_verbatim import (
21
+ MAX_CRITERIA,
22
+ PROMPT_SOURCE_COMMIT,
23
+ PROMPT_SOURCE_SHA256,
24
+ Translated,
25
+ build_messages,
26
+ criteria_pairs,
27
+ option_label,
28
+ option_text,
29
+ serialize_state,
30
+ translate_one,
31
+ )
32
+
33
+ # Keys that must never appear inside a question object sent to the model. The linter rejects
34
+ # them; the server refuses them.
35
+ LABEL_KEYS = {"label", "labels", "expected", "passingAnswer", "passing_answer", "answer", "answers",
36
+ "target", "targets", "gold", "reference", "truth", "correct"}
37
+
38
+ CHAT_TEMPLATE_KWARGS = {"add_generation_prompt": True, "enable_thinking": False, "thinking": False}
39
+
40
+
41
+ def flatten_text(v) -> str:
42
+ """Instructions and descriptions must be strings on AINode's wire. Sources that ship an object
43
+ or a list (Kev's {question, focus}) are flattened once, at conversion time, by joining the
44
+ string values with a space. Never called on a state."""
45
+ if v is None:
46
+ return ""
47
+ if isinstance(v, str):
48
+ return v.strip()
49
+ if isinstance(v, dict):
50
+ return " ".join(s for s in (flatten_text(x) for x in v.values()) if s)
51
+ if isinstance(v, list):
52
+ return " ".join(s for s in (flatten_text(x) for x in v) if s)
53
+ return json.dumps(v, ensure_ascii=False)
54
+
55
+
56
+ def wire_keys(q: dict, order: list | None = None) -> list[str]:
57
+ """The answer keys of a question in the order the options are shown."""
58
+ t = q["type"]
59
+ crit = q.get("criteria")
60
+ if t == "choice":
61
+ keys = list(crit) if isinstance(crit, dict) else [str(c) for c in crit]
62
+ if order is not None:
63
+ assert sorted(order) == sorted(keys), "order must be a permutation of the option keys"
64
+ keys = list(order)
65
+ return keys
66
+ if t == "noul":
67
+ return ["true", "false"]
68
+ if t == "score":
69
+ return [str(i) for i in range(len(crit))]
70
+ raise ValueError(f"unknown question type {t!r}")
71
+
72
+
73
+ def reorder_choice(q: dict, order: list) -> dict:
74
+ """The same choice question with its criteria in `order` (AINode shows criteria order)."""
75
+ crit = q["criteria"]
76
+ if not isinstance(crit, dict):
77
+ crit = {str(c): None for c in crit}
78
+ return {**q, "criteria": {k: crit[k] for k in order}}
79
+
80
+
81
+ @dataclass
82
+ class Rendered:
83
+ prompt: str # the chat-templated text ending in the generation prompt
84
+ keys: list[str] # wire keys in display order
85
+ letters: list[str] # the label shown for each key, same order
86
+ cand_ids: list[int] # token id of each label at the answer boundary, same order
87
+ truncated: bool # the state was cut to fit the token budget
88
+ label_scheme: str = "ainode" # "ainode" (option_label, what /v1/decide emits) or "extended"
89
+
90
+
91
+ class Renderer:
92
+ """Renders (state, question) exactly as AINode's /v1/systemone would, with this tokenizer's
93
+ chat template, and knows the label token ids."""
94
+
95
+ def __init__(self, tokenizer, max_tokens: int = 2048):
96
+ self.tok = tokenizer
97
+ self.max_tokens = max_tokens
98
+ self._label_ids: dict[str, int] = {}
99
+ # every label AINode can emit up to 255 options must be one ordinary token at the boundary
100
+ probe = self.render_messages("x", "q?", ["o1", "o2"])
101
+ base = tokenizer.encode(probe, add_special_tokens=False)
102
+ specials = set(tokenizer.all_special_ids)
103
+ for i in range(255):
104
+ lab = option_label(i)
105
+ comb = tokenizer.encode(probe + lab, add_special_tokens=False)
106
+ suffix = comb[len(base):]
107
+ if comb[:len(base)] != base or len(suffix) != 1 or suffix[0] in specials:
108
+ break
109
+ self._label_ids[lab] = suffix[0]
110
+ self.max_options = len(self._label_ids)
111
+ if self.max_options < 26:
112
+ raise ValueError(f"only {self.max_options} labels are single tokens in this tokenizer")
113
+ # Past AINode's single-token range (68 labels on the Qwen3.5 tokenizer: A..Z, AA..AZ,
114
+ # BA..BP) the served route cannot answer anyway (its cap is 20). For the LOCAL read of a
115
+ # wider question (Jevals Banking77, 77 options) we fall back to an extended alphabet: the
116
+ # same A..Z, then every two-letter uppercase string that is one ordinary token at the
117
+ # boundary, in alphabetical order. Rendered.label_scheme says which alphabet was used.
118
+ import string
119
+ self._extended: list[tuple[str, int]] = [(L, i) for L, i in self._label_ids.items() if len(L) == 1]
120
+ for a in string.ascii_uppercase:
121
+ for b in string.ascii_uppercase:
122
+ lab = a + b
123
+ comb = tokenizer.encode(probe + lab, add_special_tokens=False)
124
+ suffix = comb[len(base):]
125
+ if comb[:len(base)] == base and len(suffix) == 1 and suffix[0] not in specials:
126
+ self._extended.append((lab, suffix[0]))
127
+ self.max_options_extended = len(self._extended)
128
+
129
+ def render_messages(self, state_text: str, question: str, options: list[str],
130
+ letters: list[str] | None = None) -> str:
131
+ messages = build_messages(state_text, None, question, options)
132
+ if letters is not None:
133
+ # the extended alphabet: swap AINode's letters for ours in the option lines, nothing else
134
+ user = messages[1]["content"]
135
+ head, _, rest = user.partition("\nOPTIONS:\n")
136
+ lines = rest.split("\n")
137
+ for i in range(len(options)):
138
+ assert lines[i].startswith(f"{option_label(i)}. ")
139
+ lines[i] = f"{letters[i]}. " + lines[i][len(option_label(i)) + 2:]
140
+ messages[1]["content"] = head + "\nOPTIONS:\n" + "\n".join(lines)
141
+ return self.tok.apply_chat_template(messages, tokenize=False, **CHAT_TEMPLATE_KWARGS)
142
+
143
+ def render(self, state, q: dict, order: list | None = None) -> Rendered:
144
+ if q["type"] == "choice" and order is not None:
145
+ q = reorder_choice(q, order)
146
+ crit = q.get("criteria")
147
+ if q["type"] == "choice" and isinstance(crit, dict) and len(crit) > MAX_CRITERIA:
148
+ # AINode's route refuses this many options (its top-20 logprob read); the local read
149
+ # renders them the same way and scores every label directly off the logits
150
+ pairs = criteria_pairs("q", crit, "choice")
151
+ t = Translated("choice", str(q.get("instructions", "")).strip(),
152
+ [option_text(n, d) for n, d in pairs], [n for n, _ in pairs])
153
+ else:
154
+ t = translate_one("q", q)
155
+ keys = wire_keys(q, order)
156
+ n_opt = len(t.names)
157
+ scheme = "ainode"
158
+ if n_opt <= self.max_options:
159
+ letters = [option_label(i) for i in range(n_opt)]
160
+ cand_ids = [self._label_ids[L] for L in letters]
161
+ shown = None
162
+ elif n_opt <= self.max_options_extended:
163
+ letters = [L for L, _ in self._extended[:n_opt]]
164
+ cand_ids = [i for _, i in self._extended[:n_opt]]
165
+ shown = letters
166
+ scheme = "extended"
167
+ else:
168
+ raise ValueError(f"{n_opt} options exceed the {self.max_options_extended} single-token labels")
169
+ state_text = serialize_state(state)
170
+ prompt = self.render_messages(state_text, t.question, t.options, shown)
171
+ truncated = False
172
+ n = len(self.tok.encode(prompt, add_special_tokens=False))
173
+ if n > self.max_tokens:
174
+ # cut the state from its end so the question, options and template survive intact
175
+ overhead = n - len(self.tok.encode(state_text, add_special_tokens=False))
176
+ keep = max(self.max_tokens - overhead - 4, 16)
177
+ ids = self.tok.encode(state_text, add_special_tokens=False)[:keep]
178
+ state_text = self.tok.decode(ids) + " [truncated]"
179
+ prompt = self.render_messages(state_text, t.question, t.options, shown)
180
+ truncated = True
181
+ return Rendered(prompt, keys, letters, cand_ids, truncated, scheme)
182
+
183
+
184
+ def answer_from_probs(q: dict, keys: list[str], probs: list[float]) -> dict:
185
+ """The Jev wire answer for one question from its probabilities over `keys` (display order).
186
+ choice: {type, choice, confidence, probabilities}; noul: {type, noul}; score: {type, score,
187
+ confidence, legend, probabilities}. Ties: choice -> the option listed first; score -> the
188
+ lower level (the Jevals rule). Confidence is (p_max - 1/n) / (1 - 1/n), the chance-corrected
189
+ top probability AINode's route also reports."""
190
+ t = q["type"]
191
+ if t == "noul":
192
+ return {"type": "noul", "noul": round(float(probs[keys.index("true")]), 6)}
193
+ best = 0
194
+ for i, p in enumerate(probs):
195
+ if p > probs[best]:
196
+ best = i
197
+ n = len(keys)
198
+ p_max = float(probs[best])
199
+ confidence = 1.0 if n == 1 else (p_max - 1.0 / n) / (1.0 - 1.0 / n)
200
+ if t == "choice":
201
+ return {"type": "choice", "choice": keys[best], "confidence": round(confidence, 6),
202
+ "probabilities": {k: round(float(p), 6) for k, p in zip(keys, probs)}}
203
+ expected = sum(int(k) * float(p) for k, p in zip(keys, probs))
204
+ crit = q.get("criteria") or []
205
+ legend = {str(i): (c if isinstance(c, str) else str(c)) for i, c in enumerate(crit)} if isinstance(crit, list) \
206
+ else {str(i): k for i, k in enumerate(crit)}
207
+ return {"type": "score", "score": round(expected, 6), "confidence": round(confidence, 6),
208
+ "legend": legend, "probabilities": {k: round(float(p), 6) for k, p in zip(keys, probs)}}
temperatures.json ADDED
@@ -0,0 +1,77 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "temperatures": {
3
+ "choice": 1.2321,
4
+ "noul": 1.297,
5
+ "score": 1.1423
6
+ },
7
+ "applied_target": "train",
8
+ "calib_file": "/workspace/jeb/data-v1/calib.jsonl",
9
+ "n": {
10
+ "choice": 225,
11
+ "noul": 147,
12
+ "score": 549
13
+ },
14
+ "fits": {
15
+ "hard": {
16
+ "choice": {
17
+ "T": 0.8349,
18
+ "nll_before": 0.3342,
19
+ "nll_after": 0.33
20
+ },
21
+ "noul": {
22
+ "T": 0.9013,
23
+ "nll_before": 0.3097,
24
+ "nll_after": 0.3086
25
+ },
26
+ "score": {
27
+ "T": 0.7558,
28
+ "nll_before": 0.883,
29
+ "nll_after": 0.8662
30
+ }
31
+ },
32
+ "train": {
33
+ "choice": {
34
+ "T": 1.2321,
35
+ "nll_before": 0.4621,
36
+ "nll_after": 0.4537
37
+ },
38
+ "noul": {
39
+ "T": 1.297,
40
+ "nll_before": 0.3953,
41
+ "nll_after": 0.387
42
+ },
43
+ "score": {
44
+ "T": 1.1423,
45
+ "nll_before": 1.059,
46
+ "nll_after": 1.055
47
+ }
48
+ }
49
+ },
50
+ "nll_before": {
51
+ "choice": 0.4621,
52
+ "noul": 0.3953,
53
+ "score": 1.059
54
+ },
55
+ "nll_after": {
56
+ "choice": 0.4537,
57
+ "noul": 0.387,
58
+ "score": 1.055
59
+ },
60
+ "ece_before": {
61
+ "choice": 0.1062,
62
+ "noul": 0.0737,
63
+ "score": 0.079
64
+ },
65
+ "ece_after": {
66
+ "choice": 0.12,
67
+ "noul": 0.0951,
68
+ "score": 0.1082
69
+ },
70
+ "accuracy": {
71
+ "choice": 0.9111,
72
+ "noul": 0.898,
73
+ "score": 0.6503
74
+ },
75
+ "score_targets": "ordinal",
76
+ "score_ordinal_adjacent": 0.2
77
+ }
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0997f410c57a1f4e53b09e4be8f4a172d90edd9564368fb0847030937229b9f3
3
+ size 12809320
tokenizer_config.json ADDED
@@ -0,0 +1,305 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "added_tokens_decoder": {
4
+ "248044": {
5
+ "content": "<|endoftext|>",
6
+ "lstrip": false,
7
+ "normalized": false,
8
+ "rstrip": false,
9
+ "single_word": false,
10
+ "special": true
11
+ },
12
+ "248045": {
13
+ "content": "<|im_start|>",
14
+ "lstrip": false,
15
+ "normalized": false,
16
+ "rstrip": false,
17
+ "single_word": false,
18
+ "special": true
19
+ },
20
+ "248046": {
21
+ "content": "<|im_end|>",
22
+ "lstrip": false,
23
+ "normalized": false,
24
+ "rstrip": false,
25
+ "single_word": false,
26
+ "special": true
27
+ },
28
+ "248047": {
29
+ "content": "<|object_ref_start|>",
30
+ "lstrip": false,
31
+ "normalized": false,
32
+ "rstrip": false,
33
+ "single_word": false,
34
+ "special": true
35
+ },
36
+ "248048": {
37
+ "content": "<|object_ref_end|>",
38
+ "lstrip": false,
39
+ "normalized": false,
40
+ "rstrip": false,
41
+ "single_word": false,
42
+ "special": true
43
+ },
44
+ "248049": {
45
+ "content": "<|box_start|>",
46
+ "lstrip": false,
47
+ "normalized": false,
48
+ "rstrip": false,
49
+ "single_word": false,
50
+ "special": true
51
+ },
52
+ "248050": {
53
+ "content": "<|box_end|>",
54
+ "lstrip": false,
55
+ "normalized": false,
56
+ "rstrip": false,
57
+ "single_word": false,
58
+ "special": true
59
+ },
60
+ "248051": {
61
+ "content": "<|quad_start|>",
62
+ "lstrip": false,
63
+ "normalized": false,
64
+ "rstrip": false,
65
+ "single_word": false,
66
+ "special": true
67
+ },
68
+ "248052": {
69
+ "content": "<|quad_end|>",
70
+ "lstrip": false,
71
+ "normalized": false,
72
+ "rstrip": false,
73
+ "single_word": false,
74
+ "special": true
75
+ },
76
+ "248053": {
77
+ "content": "<|vision_start|>",
78
+ "lstrip": false,
79
+ "normalized": false,
80
+ "rstrip": false,
81
+ "single_word": false,
82
+ "special": true
83
+ },
84
+ "248054": {
85
+ "content": "<|vision_end|>",
86
+ "lstrip": false,
87
+ "normalized": false,
88
+ "rstrip": false,
89
+ "single_word": false,
90
+ "special": true
91
+ },
92
+ "248055": {
93
+ "content": "<|vision_pad|>",
94
+ "lstrip": false,
95
+ "normalized": false,
96
+ "rstrip": false,
97
+ "single_word": false,
98
+ "special": true
99
+ },
100
+ "248056": {
101
+ "content": "<|image_pad|>",
102
+ "lstrip": false,
103
+ "normalized": false,
104
+ "rstrip": false,
105
+ "single_word": false,
106
+ "special": true
107
+ },
108
+ "248057": {
109
+ "content": "<|video_pad|>",
110
+ "lstrip": false,
111
+ "normalized": false,
112
+ "rstrip": false,
113
+ "single_word": false,
114
+ "special": true
115
+ },
116
+ "248058": {
117
+ "content": "<tool_call>",
118
+ "lstrip": false,
119
+ "normalized": false,
120
+ "rstrip": false,
121
+ "single_word": false,
122
+ "special": false
123
+ },
124
+ "248059": {
125
+ "content": "</tool_call>",
126
+ "lstrip": false,
127
+ "normalized": false,
128
+ "rstrip": false,
129
+ "single_word": false,
130
+ "special": false
131
+ },
132
+ "248060": {
133
+ "content": "<|fim_prefix|>",
134
+ "lstrip": false,
135
+ "normalized": false,
136
+ "rstrip": false,
137
+ "single_word": false,
138
+ "special": false
139
+ },
140
+ "248061": {
141
+ "content": "<|fim_middle|>",
142
+ "lstrip": false,
143
+ "normalized": false,
144
+ "rstrip": false,
145
+ "single_word": false,
146
+ "special": false
147
+ },
148
+ "248062": {
149
+ "content": "<|fim_suffix|>",
150
+ "lstrip": false,
151
+ "normalized": false,
152
+ "rstrip": false,
153
+ "single_word": false,
154
+ "special": false
155
+ },
156
+ "248063": {
157
+ "content": "<|fim_pad|>",
158
+ "lstrip": false,
159
+ "normalized": false,
160
+ "rstrip": false,
161
+ "single_word": false,
162
+ "special": false
163
+ },
164
+ "248064": {
165
+ "content": "<|repo_name|>",
166
+ "lstrip": false,
167
+ "normalized": false,
168
+ "rstrip": false,
169
+ "single_word": false,
170
+ "special": false
171
+ },
172
+ "248065": {
173
+ "content": "<|file_sep|>",
174
+ "lstrip": false,
175
+ "normalized": false,
176
+ "rstrip": false,
177
+ "single_word": false,
178
+ "special": false
179
+ },
180
+ "248066": {
181
+ "content": "<tool_response>",
182
+ "lstrip": false,
183
+ "normalized": false,
184
+ "rstrip": false,
185
+ "single_word": false,
186
+ "special": false
187
+ },
188
+ "248067": {
189
+ "content": "</tool_response>",
190
+ "lstrip": false,
191
+ "normalized": false,
192
+ "rstrip": false,
193
+ "single_word": false,
194
+ "special": false
195
+ },
196
+ "248068": {
197
+ "content": "<think>",
198
+ "lstrip": false,
199
+ "normalized": false,
200
+ "rstrip": false,
201
+ "single_word": false,
202
+ "special": false
203
+ },
204
+ "248069": {
205
+ "content": "</think>",
206
+ "lstrip": false,
207
+ "normalized": false,
208
+ "rstrip": false,
209
+ "single_word": false,
210
+ "special": false
211
+ },
212
+ "248070": {
213
+ "content": "<|audio_start|>",
214
+ "lstrip": false,
215
+ "normalized": false,
216
+ "rstrip": false,
217
+ "single_word": false,
218
+ "special": true
219
+ },
220
+ "248071": {
221
+ "content": "<|audio_end|>",
222
+ "lstrip": false,
223
+ "normalized": false,
224
+ "rstrip": false,
225
+ "single_word": false,
226
+ "special": true
227
+ },
228
+ "248072": {
229
+ "content": "<tts_pad>",
230
+ "lstrip": false,
231
+ "normalized": false,
232
+ "rstrip": false,
233
+ "single_word": false,
234
+ "special": true
235
+ },
236
+ "248073": {
237
+ "content": "<tts_text_bos>",
238
+ "lstrip": false,
239
+ "normalized": false,
240
+ "rstrip": false,
241
+ "single_word": false,
242
+ "special": true
243
+ },
244
+ "248074": {
245
+ "content": "<tts_text_eod>",
246
+ "lstrip": false,
247
+ "normalized": false,
248
+ "rstrip": false,
249
+ "single_word": false,
250
+ "special": true
251
+ },
252
+ "248075": {
253
+ "content": "<tts_text_bos_single>",
254
+ "lstrip": false,
255
+ "normalized": false,
256
+ "rstrip": false,
257
+ "single_word": false,
258
+ "special": true
259
+ },
260
+ "248076": {
261
+ "content": "<|audio_pad|>",
262
+ "lstrip": false,
263
+ "normalized": false,
264
+ "rstrip": false,
265
+ "single_word": false,
266
+ "special": true
267
+ }
268
+ },
269
+ "additional_special_tokens": [
270
+ "<|im_start|>",
271
+ "<|im_end|>",
272
+ "<|object_ref_start|>",
273
+ "<|object_ref_end|>",
274
+ "<|box_start|>",
275
+ "<|box_end|>",
276
+ "<|quad_start|>",
277
+ "<|quad_end|>",
278
+ "<|vision_start|>",
279
+ "<|vision_end|>",
280
+ "<|vision_pad|>",
281
+ "<|image_pad|>",
282
+ "<|video_pad|>"
283
+ ],
284
+ "bos_token": null,
285
+ "chat_template": "{%- set image_count = namespace(value=0) %}\n{%- set video_count = namespace(value=0) %}\n{%- macro render_content(content, do_vision_count, is_system_content=false) %}\n {%- if content is string %}\n {{- content }}\n {%- elif content is iterable and content is not mapping %}\n {%- for item in content %}\n {%- if 'image' in item or 'image_url' in item or item.type == 'image' %}\n {%- if is_system_content %}\n {{- raise_exception('System message cannot contain images.') }}\n {%- endif %}\n {%- if do_vision_count %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- endif %}\n {%- if add_vision_id %}\n {{- 'Picture ' ~ image_count.value ~ ': ' }}\n {%- endif %}\n {{- '<|vision_start|><|image_pad|><|vision_end|>' }}\n {%- elif 'video' in item or item.type == 'video' %}\n {%- if is_system_content %}\n {{- raise_exception('System message cannot contain videos.') }}\n {%- endif %}\n {%- if do_vision_count %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- endif %}\n {%- if add_vision_id %}\n {{- 'Video ' ~ video_count.value ~ ': ' }}\n {%- endif %}\n {{- '<|vision_start|><|video_pad|><|vision_end|>' }}\n {%- elif 'text' in item %}\n {{- item.text }}\n {%- else %}\n {{- raise_exception('Unexpected item type in content.') }}\n {%- endif %}\n {%- endfor %}\n {%- elif content is none or content is undefined %}\n {{- '' }}\n {%- else %}\n {{- raise_exception('Unexpected content type.') }}\n {%- endif %}\n{%- endmacro %}\n{%- if not messages %}\n {{- raise_exception('No messages provided.') }}\n{%- endif %}\n{%- set reasoning_instructions = '' %}\n{%- if enable_thinking is undefined or enable_thinking is true %}\n {%- set resolved_reasoning_effort = reasoning_effort|default('xhigh') %}\n {%- if resolved_reasoning_effort not in ('xhigh', 'medium', 'low') %}\n {{- raise_exception('Unexpected reasoning effort ' ~ reasoning_effort ~ '. Supported types are xhigh (default), medium, and low.') }}\n {%- endif %}\n {%- if resolved_reasoning_effort == 'xhigh' %}\n {%- set reasoning_instructions = 'Reasoning effort is set to xhigh. Please think carefully through the task, validate key assumptions, consider plausible alternatives, and prioritize correctness, consistency, and clarity in the final answer.' %}\n {%- elif resolved_reasoning_effort == 'low' %}\n {%- set reasoning_instructions = 'Reasoning effort is set to low. Keep your thinking brief and focused, moving directly to the conclusion without unnecessary elaboration.' %}\n {%- endif %}\n{%- endif %}\n{%- if tools and tools is iterable and tools is not mapping %}\n {{- '<|im_start|>system\\n' }}\n {%- if reasoning_instructions %}\n {{- reasoning_instructions + '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou have access to the following functions:\\n\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\" }}\n {{- '\\n\\nIf you choose to call a function ONLY reply in the following format with NO suffix:\\n\\n<tool_call>\\n<function=example_function_name>\\n<parameter=example_parameter_1>\\nvalue_1\\n</parameter>\\n<parameter=example_parameter_2>\\nThis is the value for the second parameter\\nthat can span\\nmultiple lines\\n</parameter>\\n</function>\\n</tool_call>\\n\\n<IMPORTANT>\\nReminder:\\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\\n- Required parameters MUST be specified\\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\\n</IMPORTANT>' }}\n {%- if messages[0].role == 'system' %}\n {%- set content = render_content(messages[0].content, false, true)|trim %}\n {%- if content %}\n {{- '\\n\\n' + content }}\n {%- endif %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {%- set content = render_content(messages[0].content, false, true)|trim %}\n {%- if content %}\n {{- '<|im_start|>system\\n' + (reasoning_instructions + '\\n\\n' if reasoning_instructions else '') + content + '<|im_end|>\\n' }}\n {%- elif reasoning_instructions %}\n {{- '<|im_start|>system\\n' + reasoning_instructions + '<|im_end|>\\n' }}\n {%- endif %}\n {%- elif reasoning_instructions %}\n {{- '<|im_start|>system\\n' + reasoning_instructions + '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}\n{%- for message in messages[::-1] %}\n {%- set index = (messages|length - 1) - loop.index0 %}\n {%- if ns.multi_step_tool and message.role == \"user\" %}\n {%- set content = render_content(message.content, false)|trim %}\n {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}\n {%- set ns.multi_step_tool = false %}\n {%- set ns.last_query_index = index %}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if ns.multi_step_tool %}\n {{- raise_exception('No user query found in messages.') }}\n{%- endif %}\n{%- for message in messages %}\n {%- set content = render_content(message.content, true)|trim %}\n {%- if message.role == \"system\" %}\n {%- if not loop.first %}\n {{- raise_exception('System message must be at the beginning.') }}\n {%- endif %}\n {%- elif message.role == \"user\" %}\n {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {%- set reasoning_content = '' %}\n {%- if message.reasoning_content is string %}\n {%- set reasoning_content = message.reasoning_content %}\n {%- endif %}\n {%- set reasoning_content = reasoning_content|trim %}\n {%- if preserve_thinking is undefined or preserve_thinking is true or loop.index0 > ns.last_query_index %}\n {{- '<|im_start|>' + message.role + '\\n<think>\\n' + reasoning_content + '\\n</think>\\n\\n' + content }}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}\n {%- for tool_call in message.tool_calls %}\n {%- if tool_call.function is defined %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {%- if loop.first %}\n {%- if content|trim %}\n {{- '\\n\\n<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- else %}\n {{- '<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- endif %}\n {%- else %}\n {{- '\\n<tool_call>\\n<function=' + tool_call.name + '>\\n' }}\n {%- endif %}\n {%- if tool_call.arguments is defined and tool_call.arguments != '' %}\n {%- for args_name, args_value in tool_call.arguments|items %}\n {{- '<parameter=' + args_name + '>\\n' }}\n {%- set args_value = args_value | string if args_value is string else args_value | tojson | safe %}\n {{- args_value }}\n {{- '\\n</parameter>\\n' }}\n {%- endfor %}\n {%- endif %}\n {{- '</function>\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.previtem and loop.previtem.role != \"tool\" %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- content }}\n {{- '\\n</tool_response>' }}\n {%- if not loop.last and loop.nextitem.role != \"tool\" %}\n {{- '<|im_end|>\\n' }}\n {%- elif loop.last %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- else %}\n {{- raise_exception('Unexpected message role.') }}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n {%- if enable_thinking is defined and enable_thinking is false %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- else %}\n {{- '<think>\\n' }}\n {%- endif %}\n{%- endif %}",
286
+ "clean_up_tokenization_spaces": false,
287
+ "eos_token": "<|im_end|>",
288
+ "errors": "replace",
289
+ "model_max_length": 262144,
290
+ "pad_token": "<|endoftext|>",
291
+ "split_special_tokens": false,
292
+ "tokenizer_class": "Qwen2Tokenizer",
293
+ "unk_token": null,
294
+ "add_bos_token": false,
295
+ "pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
296
+ "extra_special_tokens": {
297
+ "audio_bos_token": "<|audio_start|>",
298
+ "audio_eos_token": "<|audio_end|>",
299
+ "audio_token": "<|audio_pad|>",
300
+ "image_token": "<|image_pad|>",
301
+ "video_token": "<|video_pad|>",
302
+ "vision_bos_token": "<|vision_start|>",
303
+ "vision_eos_token": "<|vision_end|>"
304
+ }
305
+ }
vocab.json ADDED
The diff for this file is too large to render. See raw diff