fix(api): don't iterate a string banned_tokens character by character (#2333) (#2336)

* fix(api): don't iterate a string banned_tokens character by character (#2333)

banned_tokens/banned_strings are consumed as a list of substrings. When a
caller supplies a bare string instead, `banned_tokens[:ban_token_max]`
slices it and `for tok in banned_tokens` walks it one character at a time,
so every letter in the value becomes its own banned substring. Banning
common letters like "e" makes generation collapse into garbage, with no
error to point at the cause.

Coerce a string value into a single-element list, matching how the OpenAI
`stop` parameter is already normalized in transform_genparams.

* fix(api): parse a JSON-array string in banned_tokens instead of inerting it

Per review: coercing the string form to a single-element list left the
reported gendefaults case a seemingly-effective no-op. Parse a JSON array
supplied as a string so the reported config bans "exclude" as intended,
and keep the bare-string form ("exclude") working as a single substring ban.

---------

Co-authored-by: Anai-Guo <antai12232931@anaiguo.com>
This commit is contained in:
Tai An
2026-07-17 19:46:02 -07:00
committed by GitHub
parent 3b6d698616
commit 8b85ba1a33
+19 -2
View File
@@ -2077,6 +2077,23 @@ def load_model(model_filename):
ret = handle.load_model(inputs)
return ret
def coerce_ban_list(value):
# banned tokens/strings are consumed as a list of substrings. A bare string satisfies
# every operation there, but iterates character by character, banning single letters.
if not value:
return []
if not isinstance(value, str):
return value
stripped = value.strip()
if stripped.startswith('[') and stripped.endswith(']'): # a JSON array sent as a string, e.g. through gendefaults
try:
parsed = json.loads(stripped)
if isinstance(parsed, list):
return [str(item) for item in parsed]
except json.JSONDecodeError:
pass
return [value]
def generate(genparams, stream_flag=False):
global maxctx, args, currentusergenkey, totalgens, pendingabortkey
default_adapter = {} if chatcompl_adapter is None else chatcompl_adapter
@@ -2141,8 +2158,8 @@ def generate(genparams, stream_flag=False):
min_p = 0.002
logit_biases = genparams.get('logit_bias', {})
render_special = genparams.get('render_special', False)
banned_strings = genparams.get('banned_strings', []) # SillyTavern uses that name
banned_tokens = genparams.get('banned_tokens', banned_strings)
banned_strings = coerce_ban_list(genparams.get('banned_strings', [])) # SillyTavern uses that name
banned_tokens = coerce_ban_list(genparams.get('banned_tokens', banned_strings))
bypass_eos_token = genparams.get('bypass_eos', False)
tool_call_fix = genparams.get('using_openai_tools', False)
custom_token_bans = genparams.get('custom_token_bans', '')