Text Classification
Transformers
Safetensors
English
Chinese
qwen3_5
image-text-to-text
decision-model
system-one
decision-index
lora-merged
Instructions to use PelaAI/KnowLine-4B-Gen5.5 with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use PelaAI/KnowLine-4B-Gen5.5 with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-classification", model="PelaAI/KnowLine-4B-Gen5.5")# pip install -U transformers accelerate # Load model directly from transformers import AutoProcessor, AutoModelForMultimodalLM processor = AutoProcessor.from_pretrained("PelaAI/KnowLine-4B-Gen5.5") model = AutoModelForMultimodalLM.from_pretrained("PelaAI/KnowLine-4B-Gen5.5", device_map="auto") - Notebooks
- Google Colab
- Kaggle
File size: 12,264 Bytes
78d8ece | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 | # knowline_layout.py: release copy of openjev.header_layout (OpenJev src/openjev/header_layout.py), generated by
# make_knowline_layout.py. Only the helper import differs (knowline_server instead of openjev.systemone.prompt).
# source sha256: 8dfa4571ac18c1f890bb1662ac8241b156417a49dc05d4ebec8257406a3a5011
"""Request layouts beyond the default "chat" layout of openjev.systemone.prompt: the header (the request's question list
before the state), read twice, and option rotations. Spec: docs/plans/2026-10-22-header-layout.md.
Pure Python. The only outside object is the model's tokenizer or HF processor (`template`): its chat template
(apply_chat_template) renders the turns and its encode() counts tokens for the read-twice cut. With every switch off,
render_request() / render() give byte-identical prompts to openjev.systemone.prompt.render(..., style="chat"), the layout
our training data and release serving use (tests/test_header_layout.py checks this).
from openjev.header_layout import Layout, render, render_request
text = render({"state": ..., "questions": {...}}, 0, tokenizer, Layout(header=True)) # one question
r = render_request(request, tokenizer, Layout(header=True, read_twice=True), rotations=(0, 1)) # a whole request
r["prompts"][qid][offset] -> (prompt text ending in "Answer:", answer keys in shown order)
Option rotations are a scoring technique: score the question at offsets first_pass_offsets(n), map each offset's label
distribution back with to_original(), average them with equal weight (combine_rotations), and when the two first-pass
offsets pick different options of a 3+ option question also score gated_offsets(n) and average all of them.
"""
import uuid
from dataclasses import dataclass
from knowline_server import ANSWER, DEFAULT_QUESTION, INSTRUCTION, find_labels, options_of, render_value, state_messages
HEADER_HEAD = "Questions that will be asked about the conversation or state below:\n"
HEADER_ITEM = "- "
READ_AGAIN = "Read the same input again before answering:\n"
TWICE_MAX_TOKENS = 3000
_KW = dict(tokenize=False, add_generation_prompt=True, enable_thinking=False)
@dataclass(frozen=True)
class Layout:
header: bool = False # list every question of the request before the state
header_min_questions: int = 1 # requests with fewer questions keep the default layout (1 = every request)
read_twice: bool = False # write the whole input twice, joined by READ_AGAIN, ...
twice_max_tokens: int = TWICE_MAX_TOKENS # ... in text-only requests of at most this many tokens read once
# ------------------------------------------------------------------ pieces
def question_text(q):
"""The question stem, as the question block shows it."""
return render_value(q["instructions"]) if q.get("instructions") is not None else DEFAULT_QUESTION
def header_text(questions):
"""'Questions that will be asked ...:\\n- <stem>\\n- <stem>\\n\\n' in request order (stems only, no options)."""
return HEADER_HEAD + "\n".join(HEADER_ITEM + question_text(q) for q in questions.values()) + "\n\n"
def rotation(n, offset):
"""Original option indices in shown order for a cyclic rotation by `offset` (offset 0 = request order)."""
offset %= n
return list(range(offset, n)) + list(range(offset))
def question_block(q, labels, offset=0):
"""-> (block text, answer keys in shown order). Offset 0 is the default layout's block byte for byte."""
keys, texts = options_of(q)
order = rotation(len(keys), offset)
lines = "".join(f"{labels[j]}. {texts[i]}\n" for j, i in enumerate(order))
return f"Question: {question_text(q)}\nOptions:\n{lines.rstrip()}", [keys[i] for i in order]
def _prepend(msgs, text):
"""`text` before the first non-system turn: prefixed to that turn's content when it is a user turn, else its own user
turn (without the trailing newlines) inserted there."""
i = next((k for k, m in enumerate(msgs) if m.get("role") != "system"), len(msgs))
if i < len(msgs) and msgs[i].get("role") == "user":
c = msgs[i].get("content")
c = [{"type": "text", "text": text}] + list(c) if isinstance(c, list) else text + (c or "")
return msgs[:i] + [{**msgs[i], "content": c}] + msgs[i + 1:]
return msgs[:i] + [{"role": "user", "content": text.rstrip("\n")}] + msgs[i:]
def _join(a, b):
"""Content of two adjacent turns of one role, folded into one turn with a blank line between them (for a text `b`
after a text or part-list `a`, exactly the default layout's fold of the ask into the last user turn)."""
if isinstance(a, list):
return a + ([{"type": "text", "text": "\n\n" + b}] if isinstance(b, str) else [{"type": "text", "text": "\n\n"}] + list(b))
if isinstance(b, list):
return [{"type": "text", "text": f"{a}\n\n"}] + list(b)
return f"{a}\n\n{b}"
def _merge_adjacent(msgs):
out = []
for m in msgs:
if out and out[-1].get("role") == m.get("role") and m.get("role") in ("user", "system"):
out[-1] = {**out[-1], "content": _join(out[-1].get("content"), m.get("content"))}
else:
out.append(dict(m))
return out
def _conv(msgs, hdr, m1, m2):
"""[system turns] + [header] + state turns + ask(m1); with m2 also READ_AGAIN + the same non-system turns + ask(m2)."""
if hdr:
msgs = _prepend(msgs, hdr)
conv = msgs + [{"role": "user", "content": INSTRUCTION + "\n\n" + m1}]
if m2 is None:
return conv
again = [m for m in conv if m.get("role") != "system"] # system turns appear once, at the start
again[-1] = {"role": "user", "content": INSTRUCTION + "\n\n" + m2}
return conv + _prepend(again, READ_AGAIN)
def _scaffold(template, state, hdr, twice):
"""-> (prefix, middle or None, ending, media). The question block goes at the markers: once at m1, twice at m1 and m2."""
m1 = f"OPENJEV_{uuid.uuid4().hex}"
m2 = f"OPENJEV_{uuid.uuid4().hex}" if twice else None
msgs, media = state_messages(state)
try:
text = template.apply_chat_template(_conv(msgs, hdr, m1, m2), **_KW)
except Exception:
try:
if not twice: # the default layout's fold: the ask into the last user turn (strict-alternation templates)
conv = _conv(msgs, hdr, m1, None)
if len(conv) < 2 or conv[-2].get("role") != "user":
raise ValueError("last turn is not a user turn")
conv = conv[:-2] + [{**conv[-2], "content": _join(conv[-2]["content"], conv[-1]["content"])}]
else:
conv = _merge_adjacent(_conv(msgs, hdr, m1, m2))
text = template.apply_chat_template(conv, **_KW)
except Exception: # roles the template rejects: the whole chat as one user message, as the default layout does
msgs, media = [{"role": "user", "content": render_value(state)}], []
text = template.apply_chat_template(_conv(msgs, hdr, m1, m2), **_KW)
if text.count(m1) != 1 or (m2 is not None and text.count(m2) != 1):
raise ValueError("chat template dropped or duplicated the question slot")
prefix, rest = text.split(m1)
if m2 is None:
return prefix, None, rest, media
middle, ending = rest.split(m2)
return prefix, middle, ending, media
_LABELS = {}
def labels_and_ids(template):
"""The default engine's label set and token ids: single-token labels A..Z, AA.. checked right after the real prompt
ending (as openjev.systemone.engine.Engine finds them)."""
key = id(template)
if key not in _LABELS:
prefix, _, ending, _ = _scaffold(template, "x", None, False)
block, _ = question_block({"type": "noul"}, ["A", "B"])
_LABELS[key] = (template, find_labels(getattr(template, "tokenizer", template), prefix + block + ending + ANSWER))
return _LABELS[key][1]
def default_labels(template):
return labels_and_ids(template)[0]
# ------------------------------------------------------------------ rendering
def render_request(request, template, layout=Layout(), rotations=(0,), labels=None):
"""-> {"prefix": shared prefix text, "twice": bool, "once_tokens": int | None, "media": [...],
"prompts": {qid: {offset: (prompt text, answer keys in shown order)}}}.
`request` is a /v1/systemone body ({"state", "questions"}). `rotations`: cyclic option offsets (see rotation()) for every
question, or a function (question id, number of options) -> offsets, e.g. lambda qid, n: first_pass_offsets(n)."""
state, questions = request.get("state", ""), request.get("questions") or {}
if not questions:
raise ValueError("no questions")
labels = labels or default_labels(template)
hdr = header_text(questions) if layout.header and len(questions) >= layout.header_min_questions else None
prefix, _, ending, media = _scaffold(template, state, hdr, False)
offs = rotations if callable(rotations) else (lambda qid, n: rotations)
blocks = {qid: {o: question_block(q, labels, o) for o in dict.fromkeys(offs(qid, len(options_of(q)[0])))}
for qid, q in questions.items()}
blocks = {qid: bo for qid, bo in blocks.items() if bo}
once = {qid: {o: (prefix + b + ending + ANSWER, keys) for o, (b, keys) in bo.items()} for qid, bo in blocks.items()}
out = {"prefix": prefix, "twice": False, "once_tokens": None, "media": media, "prompts": once}
if not layout.read_twice or media: # read twice is text-only; a request with media is read once
return out
tok = getattr(template, "tokenizer", template)
n = lambda s: len(tok.encode(s, add_special_tokens=False))
p = n(prefix)
first = {qid: (prefix + question_block(q, labels, 0)[0] + ending + ANSWER) for qid, q in questions.items()}
out["once_tokens"] = p + sum(n(t) - p for t in first.values()) # shared prefix + every question row, read once
if out["once_tokens"] > layout.twice_max_tokens:
return out
prefix2, middle, ending2, _ = _scaffold(template, state, hdr, True)
out.update(prefix=prefix2, twice=True,
prompts={qid: {o: (prefix2 + b + middle + b + ending2 + ANSWER, keys) for o, (b, keys) in bo.items()}
for qid, bo in blocks.items()})
return out
def render(request, question_idx, template, layout=Layout(), rotation_offset=0, labels=None):
"""The prompt text of one question (by position in the request or by id), ending in "Answer:"."""
qids = list((request.get("questions") or {}))
qid = qids[question_idx] if isinstance(question_idx, int) else question_idx
return render_request(request, template, layout, (rotation_offset,), labels)["prompts"][qid][rotation_offset][0]
# ------------------------------------------------------------------ rotations (scoring side)
def first_pass_offsets(n):
"""Request order and its rotation by n // 2 (a swap for two options)."""
return list(dict.fromkeys([0, n // 2])) if n >= 2 else [0]
def gated_offsets(n):
"""Second pass for 3+ option questions whose first-pass offsets disagree: the other four-order rotations."""
first = first_pass_offsets(n)
return [o for o in dict.fromkeys([n // 4, 3 * n // 4]) if o not in first] if n >= 3 else []
def to_original(shown_probs, offset):
"""A label distribution in shown order -> the same distribution in request (original option) order."""
n = len(shown_probs)
out = [0.0] * n
for j, i in enumerate(rotation(n, offset)):
out[i] = shown_probs[j]
return out
def argmax(p):
return max(range(len(p)), key=p.__getitem__)
def combine_rotations(by_offset):
"""{offset: distribution in shown order} -> equal-weight mean in request order (each already tempered)."""
rows = [to_original(p, o) for o, p in by_offset.items()]
return [sum(r[i] for r in rows) / len(rows) for i in range(len(rows[0]))]
def needs_gate(by_offset):
"""True when the first-pass offsets of a 3+ option question pick different original options."""
n = len(next(iter(by_offset.values())))
first = first_pass_offsets(n)
if n < 3 or any(o not in by_offset for o in first):
return False
return len({argmax(to_original(by_offset[o], o)) for o in first}) > 1
|