При работе с локальными языковыми моделями через Open WebUI, vLLM, Qwen Coder, Llama или Mistral можно столкнуться с такой ошибкой:
Код:
This model's maximum context length is 100000 tokens.
However, you requested 20000 output tokens and your prompt contains
at least 80001 input tokens, for a total of at least 100001 tokens.
Please reduce the length of the input prompt or the number of
requested output tokens.
(parameter=input_tokens, value=80001)
На первый взгляд может показаться, что проблема связана с самой моделью или неправильной настройкой vLLM. На самом деле сообщение означает, что сумма входного контекста и максимально разрешённого размера ответа превысила контекстное окно модели.
В этой статье разберём:
- что означает ошибка
maximum context length; - откуда Open WebUI берёт десятки тысяч входных токенов;
- как влияет параметр
max_tokens; - почему контекст переполняется из-за истории чата;
- как на размер контекста влияют загруженные файлы;
- чем отличаются Full Context и Focused Retrieval;
- почему простое увеличение контекста не решает проблему;
- как автоматически обрезать историю через Filter Function;
- какие параметры использовать для модели с контекстом 100 000 токенов;
- как проверить защиту через Docker-журналы.
---
## Что означает ошибка
maximum context lengthРассмотрим сообщение:
Код:
This model's maximum context length is 100000 tokens.
However, you requested 20000 output tokens and your prompt contains
at least 80001 input tokens.
Здесь указаны три важных значения:
Код:
Максимальное окно модели: 100000 токенов
Запрошенный размер ответа: 20000 токенов
Входной контекст: 80001 токен
vLLM проверяет условие:
Код:
input_tokens + max_tokens <= max_model_len
В нашем случае:
Код:
80001 + 20000 = 100001
Но модель настроена только на:
Код:
100000 токенов
Поэтому запрос превышает лимит всего на один токен:
Код:
100001 > 100000
Этого достаточно, чтобы vLLM отклонил запрос ещё до начала генерации ответа.
---
## Почему максимальный вход равен 80 000 токенов
Если модель запущена с контекстом:
Код:
max_model_len = 100000
а в Open WebUI установлено:
Код:
max_tokens = 20000
то максимальный теоретический размер входного контекста равен:
Код:
100000 - 20000 = 80000 токенов
Получается:
Код:
До 80000 входных токенов + до 20000 токенов ответа = 100000
Первое недопустимое значение входа:
Код:
80001
Именно поэтому в ошибке появляется:
Код:
parameter=input_tokens, value=80001
Это не случайное число. vLLM сообщает минимальный размер входа, при котором запрос уже не помещается в заданное окно.
---
# Что входит во входной контекст
Пользователь может видеть в поле ввода всего одну короткую фразу, но Open WebUI отправляет модели значительно больше данных.
Во входной контекст могут входить:
- системный промпт;
- текущий вопрос пользователя;
- предыдущие вопросы;
- предыдущие ответы модели;
- исходный код;
- журналы работы программ;
- содержимое загруженных файлов;
- результаты поиска по базе знаний;
- RAG-фрагменты;
- результаты веб-поиска;
- схемы инструментов;
- вызовы функций;
- ответы инструментов;
- сохранённая память пользователя;
- дополнительные инструкции модели;
- служебные маркеры ролей;
- шаблон чата Qwen;
- данные, добавленные расширениями и фильтрами.
Код:
system
user-1
assistant-1
user-2
assistant-2
user-3
assistant-3
user-4
Даже если последний вопрос занимает всего 50 токенов, предыдущие ответы могут содержать десятки тысяч токенов кода, журналов и документации.
---
# Почему история чата быстро переполняет контекст
Особенно быстро контекст растёт при работе с программированием.
Например:
1. Пользователь просит создать REST API.
2. Модель генерирует 5 000 токенов кода.
3. Пользователь просит добавить PostgreSQL.
4. Модель генерирует ещё 7 000 токенов.
5. Пользователь просит добавить Redis.
6. Модель создаёт ещё 6 000 токенов.
7. Пользователь просит добавить Docker Compose.
8. Модель генерирует ещё несколько тысяч токенов.
Через несколько сообщений история может выглядеть так:
Код:
Системный промпт: 3 000 токенов
Первый вопрос и ответ: 10 000 токенов
Второй вопрос и ответ: 14 000 токенов
Третий вопрос и ответ: 12 000 токенов
Четвёртый вопрос: 2 000 токенов
---------------------------------------
Всего: 41 000 токенов
Если продолжить работу, контекст постепенно приблизится к 80 000 токенов и vLLM вернёт ошибку.
---
# Почему увеличение
max_model_len не решает проблему окончательноМожно увеличить:
Код:
--max-model-len 100000
например до:
Код:
--max-model-len 120000
Но это только отложит ошибку.
Если Open WebUI продолжит передавать всю историю, через некоторое время будет переполнено и новое окно.
Кроме того, большой контекст:
- увеличивает использование KV-кэша;
- повышает потребление видеопамяти;
- снижает возможное количество параллельных запросов;
- может уменьшать скорость обработки;
- иногда ухудшает качество внимания модели к важным фрагментам;
- создаёт дополнительную нагрузку на сервер.
---
# Быстрые способы исправить ошибку
## 1. Создать новый чат
Самый простой вариант — начать новый диалог.
В новом чате отсутствует накопленная история, поэтому тот же вопрос может успешно пройти.
Это хороший диагностический тест:
1. Создайте новый чат.
2. Отправьте тот же запрос.
3. Посмотрите, появилась ли ошибка.
Если в новом чате ошибка исчезла, значит проблема находилась именно в истории.
Недостаток метода: модель перестаёт видеть предыдущую работу.
---
## 2. Уменьшить
max_tokensПри:
Код:
max_model_len = 100000
max_tokens = 20000
под вход остаётся:
Код:
80000 токенов
Если уменьшить размер максимального ответа:
Код:
max_tokens = 10000
для входа останется:
Код:
100000 - 10000 = 90000 токенов
Запрос с 80 001 входным токеном тогда пройдёт.
Однако это временное решение. История продолжит расти и позднее снова переполнит окно.
Для большинства ответов по программированию часто достаточно:
Код:
max_tokens = 8000–12000
Значение:
Код:
max_tokens = 20000
имеет смысл для:
- длинных статей;
- генерации больших файлов;
- подробного анализа журналов;
- создания нескольких модулей проекта;
- генерации больших объёмов исходного кода.
---
## 3. Удалить старые сообщения
Можно вручную удалить старые сообщения или начать новую ветку.
Но для публичного сервиса это неудобно. Пользователь не должен самостоятельно считать токены и контролировать размер контекста.
Лучше настроить автоматическую обрезку.
---
# Как загруженные файлы переполняют контекст
Файлы могут занимать больше токенов, чем вся остальная история чата.
Например, текстовый файл содержит:
Код:
85000 символов
При приблизительном соотношении:
Код:
2.5 символа ≈ 1 токен
получаем:
Код:
85000 / 2.5 ≈ 34000 токенов
Если такой файл три раза попадёт в запрос:
Код:
34000 × 3 = 102000 токенов
Контекст переполнится ещё до учёта:
- системного промпта;
- вопросов пользователя;
- ответов модели;
- максимального ответа.
---
## Full Context и Focused Retrieval
В Open WebUI документы могут обрабатываться по-разному.
### Full Context
В режиме Full Context содержимое файла вставляется в контекст практически целиком.
Преимущества:
- модель видит весь документ;
- хорошо подходит для небольших файлов;
- можно анализировать структуру целиком.
- быстро расходуются токены;
- несколько файлов могут переполнить контекст;
- один большой PDF или TXT может занимать десятки тысяч токенов.
### Focused Retrieval
В режиме Focused Retrieval документ разбивается на фрагменты, после чего Open WebUI выбирает только наиболее релевантные части.
Например:
Код:
Chunk Size: 1000 токенов
Chunk Overlap: 100 токенов
Top K: 4
В запрос попадёт приблизительно:
Код:
4 × 1000 = 4000 токенов
а не весь документ.
Для больших книг, PDF и технической документации лучше использовать:
Код:
Focused Retrieval
Рекомендуемые начальные параметры:
Код:
Chunk Size: 700–1200
Chunk Overlap: 70–150
Top K: 3–5
---
# Надёжное решение: Filter Function в Open WebUI
Open WebUI позволяет создать фильтр, который изменяет запрос до отправки модели.
Для этого используется метод:
Python:
async def inlet(...)
Фильтр может:
- сохранить системный промпт;
- оставить только последние сообщения;
- приблизительно подсчитать токены;
- удалить старые диалоговые ходы;
- сохранить текущий вопрос;
- отклонить слишком большой запрос;
- вывести информацию об обрезке в Docker-журнал.
---
# Как должна работать защита
Рекомендуемый алгоритм:
Код:
1. Сохранить начальные system/developer-сообщения.
2. Оставить максимум несколько последних сообщений.
3. Удалить осиротевшие assistant/tool-сообщения.
4. Оценить количество входных токенов.
5. Если бюджет превышен — удалить самый старый полный ход.
6. Снова пересчитать токены.
7. Повторять, пока контекст не станет допустимым.
8. Если текущий вопрос сам слишком большой — отклонить запрос.
Важно удалять не случайное отдельное сообщение, а полный диалоговый ход:
Код:
user
assistant
При использовании инструментов ход может выглядеть так:
Код:
user
assistant с tool_call
tool
assistant
Если удалить только часть такой последовательности, модель может получить результат инструмента без соответствующего вызова.
---
# Готовая функция ограничения истории
Ниже приведён пример Filter Function для Open WebUI.
Функция:
- сохраняет начальный системный промпт;
- оставляет не более шести последних сообщений;
- ограничивает оценочный размер входа;
- удаляет самые старые полные ходы;
- сохраняет текущий запрос;
- отклоняет запрос, который невозможно уместить в бюджет;
- выводит диагностику в журнал контейнера.
Python:
"""
title: Zer0Kernel Context Window
author: zer0kernel
version: 1.2.0
description: Ограничивает историю чата и оценочный размер входного контекста.
"""
import hashlib
import json
import math
import re
from typing import Any, Optional
from pydantic import BaseModel, Field
class Filter:
CONTROL_ROLES = {"system", "developer"}
IMAGE_TYPES = {"image", "image_url", "input_image"}
TEXT_TYPES = {"text", "input_text", "output_text"}
MARKER_RE = re.compile(
r"\b(?:(?:CTX|TEST|TOKEN)[-_][A-Z0-9][A-Z0-9_-]{0,63}"
r"|M[0-9]{1,3}(?:[-_][A-Z0-9_-]{1,64})?)\b",
re.IGNORECASE,
)
class Valves(BaseModel):
priority: int = Field(
default=1000,
description=(
"Приоритет выполнения фильтра. "
"Меньшие значения выполняются раньше."
),
)
max_messages: int = Field(
default=6,
ge=1,
description=(
"Максимальное количество последних сообщений диалога. "
"Начальный системный промпт в этот лимит не входит."
),
)
max_input_tokens: int = Field(
default=30000,
ge=1,
description=(
"Максимальный оценочный размер входного контекста."
),
)
chars_per_token: float = Field(
default=2.5,
gt=0.0,
description=(
"Приблизительное количество символов на один токен."
),
)
message_overhead_tokens: int = Field(
default=16,
ge=0,
description=(
"Служебный расход токенов на каждое сообщение."
),
)
image_token_reserve: int = Field(
default=1500,
ge=0,
description=(
"Резерв токенов на одно изображение."
),
)
keep_current_user_message: bool = Field(
default=True,
description=(
"Сохранять текущий вопрос пользователя."
),
)
reject_oversized_request: bool = Field(
default=True,
description=(
"Отклонить запрос, если даже текущий ход "
"не помещается в заданный бюджет."
),
)
debug: bool = Field(
default=True,
description=(
"Выводить диагностику в журнал Open WebUI."
),
)
debug_preview_chars: int = Field(
default=0,
ge=0,
le=300,
description=(
"Количество символов сообщения для вывода в журнал. "
"Значение 0 скрывает содержимое сообщений."
),
)
def __init__(self):
self.valves = self.Valves()
def _json_text(self, value: Any) -> str:
try:
return json.dumps(
value,
ensure_ascii=False,
separators=(",", ":"),
sort_keys=True,
default=str,
)
except Exception:
return str(value)
def _content_to_text(self, content: Any) -> str:
if content is None:
return ""
if isinstance(content, str):
return content
if isinstance(content, list):
parts: list[str] = []
for item in content:
if isinstance(item, str):
parts.append(item)
continue
if isinstance(item, dict):
item_type = str(item.get("type", ""))
if item_type in self.TEXT_TYPES:
parts.append(
str(
item.get(
"text",
item.get(
"content",
item.get("value", ""),
),
)
)
)
elif item_type in self.IMAGE_TYPES:
parts.append("[IMAGE]")
else:
parts.append(self._json_text(item))
continue
parts.append(str(item))
return "\n".join(parts)
if isinstance(content, dict):
return self._json_text(content)
return str(content)
def _estimate_text_tokens(self, text: str) -> int:
if not text:
return 0
chars_per_token = max(
float(self.valves.chars_per_token),
0.1,
)
return math.ceil(
len(text) / chars_per_token
)
def _estimate_content_tokens(self, content: Any) -> int:
if content is None:
return 0
if isinstance(content, str):
return self._estimate_text_tokens(content)
if isinstance(content, list):
total = 0
for item in content:
if isinstance(item, dict):
item_type = str(item.get("type", ""))
if item_type in self.IMAGE_TYPES:
total += int(
self.valves.image_token_reserve
)
continue
if item_type in self.TEXT_TYPES:
text_value = item.get(
"text",
item.get(
"content",
item.get("value", ""),
),
)
total += self._estimate_content_tokens(
text_value
)
continue
total += self._estimate_text_tokens(
self._json_text(item)
)
return total
if isinstance(content, dict):
return self._estimate_text_tokens(
self._json_text(content)
)
return self._estimate_text_tokens(
str(content)
)
def _estimate_message_tokens(
self,
message: dict[str, Any],
) -> int:
estimated = int(
self.valves.message_overhead_tokens
)
estimated += self._estimate_content_tokens(
message.get("content")
)
for field_name in (
"name",
"tool_call_id",
"tool_calls",
"function_call",
"reasoning_content",
"images",
"audio",
"files",
"sources",
):
value = message.get(field_name)
if value not in (
None,
"",
[],
{},
):
estimated += self._estimate_text_tokens(
self._json_text(value)
)
return estimated
def _estimate_messages_tokens(
self,
messages: list[dict[str, Any]],
) -> int:
return sum(
self._estimate_message_tokens(message)
for message in messages
)
def _estimate_fixed_body_tokens(
self,
body: dict[str, Any],
) -> int:
total = 0
for field_name in (
"tools",
"functions",
):
value = body.get(field_name)
if value not in (
None,
"",
[],
{},
):
total += self._estimate_text_tokens(
self._json_text(value)
)
return total
def _split_leading_control_messages(
self,
messages: list[dict[str, Any]],
) -> tuple[
list[dict[str, Any]],
list[dict[str, Any]],
]:
control: list[dict[str, Any]] = []
conversation: list[dict[str, Any]] = []
in_control_prefix = True
for message in messages:
role = message.get("role")
if (
in_control_prefix
and role in self.CONTROL_ROLES
):
control.append(message)
continue
in_control_prefix = False
conversation.append(message)
return control, conversation
def _normalize_window_start(
self,
messages: list[dict[str, Any]],
) -> list[dict[str, Any]]:
result = list(messages)
if not result:
return result
first_user_index = next(
(
index
for index, message in enumerate(result)
if message.get("role") == "user"
),
None,
)
if first_user_index is not None:
return result[first_user_index:]
while (
result
and result[0].get("role")
in {"tool", "function"}
):
result.pop(0)
return result
def _drop_oldest_complete_turn(
self,
messages: list[dict[str, Any]],
) -> tuple[list[dict[str, Any]], bool]:
if not messages:
return [], False
next_user_index = next(
(
index
for index in range(
1,
len(messages),
)
if messages[index].get("role")
== "user"
),
None,
)
if next_user_index is None:
return list(messages), False
return messages[next_user_index:], True
def _debug_descriptor(
self,
message: dict[str, Any],
index: int,
) -> str:
role = str(message.get("role", "?"))
text = self._content_to_text(
message.get("content")
)
digest = hashlib.sha256(
text.encode(
"utf-8",
errors="replace",
)
).hexdigest()[:10]
markers = sorted(
set(
self.MARKER_RE.findall(text)
)
)
marker_text = (
"|".join(markers[:12])
if markers
else "-"
)
token_estimate = (
self._estimate_message_tokens(
message
)
)
descriptor = (
f"{index}:{role}:"
f"chars={len(text)}:"
f"tokens~={token_estimate}:"
f"sha={digest}:"
f"markers={marker_text}"
)
preview_chars = int(
self.valves.debug_preview_chars
)
if preview_chars > 0:
preview = " ".join(
text[:preview_chars].split()
)
descriptor += (
f":preview={preview!r}"
)
return descriptor
async def inlet(
self,
body: dict,
__user__: Optional[dict] = None,
) -> dict:
raw_messages = body.get("messages")
if (
not isinstance(raw_messages, list)
or not raw_messages
):
return body
messages = [
message
for message in raw_messages
if isinstance(message, dict)
]
if not messages:
return body
(
control_messages,
conversation,
) = self._split_leading_control_messages(
messages
)
original_conversation = list(
conversation
)
original_token_estimate = (
self._estimate_messages_tokens(
messages
)
)
fixed_body_tokens = (
self._estimate_fixed_body_tokens(
body
)
)
max_messages = max(
1,
int(self.valves.max_messages),
)
message_limited = conversation[
-max_messages:
]
conversation = (
self._normalize_window_start(
message_limited
)
)
dropped_by_message_limit = (
len(original_conversation)
- len(conversation)
)
dropped_turns_by_token_limit = 0
token_budget = max(
1,
int(
self.valves.max_input_tokens
),
)
while conversation:
estimated_total = (
self._estimate_messages_tokens(
control_messages
+ conversation
)
+ fixed_body_tokens
)
if estimated_total <= token_budget:
break
reduced, changed = (
self._drop_oldest_complete_turn(
conversation
)
)
if changed:
conversation = (
self._normalize_window_start(
reduced
)
)
dropped_turns_by_token_limit += 1
continue
if (
self.valves
.keep_current_user_message
):
break
conversation = []
break
final_messages = (
control_messages
+ conversation
)
final_token_estimate = (
self._estimate_messages_tokens(
final_messages
)
+ fixed_body_tokens
)
over_budget = (
final_token_estimate
> token_budget
)
if (
over_budget
and self.valves
.reject_oversized_request
):
raise ValueError(
"Zer0Kernel Context Window: "
"the request is estimated at "
f"{final_token_estimate} "
"input tokens, exceeding "
f"max_input_tokens="
f"{token_budget}. "
"Reduce the current message "
"or file context."
)
body["messages"] = final_messages
if self.valves.debug:
kept_details = ";".join(
self._debug_descriptor(
message,
index,
)
for index, message
in enumerate(final_messages)
)
print(
"[ZK_CONTEXT] "
f"raw_messages="
f"{len(raw_messages)}, "
f"valid_messages="
f"{len(messages)}, "
f"leading_control="
f"{len(control_messages)}, "
f"conversation="
f"{len(original_conversation)}"
f"->{len(conversation)}, "
f"dropped_by_message_limit="
f"{dropped_by_message_limit}, "
f"dropped_turns_by_token_limit="
f"{dropped_turns_by_token_limit}, "
f"tokens~="
f"{original_token_estimate + fixed_body_tokens}"
f"->{final_token_estimate}, "
f"budget={token_budget}, "
f"over_budget="
f"{str(over_budget).lower()}, "
f"kept=[{kept_details}]",
flush=True,
)
return body
---
# Как установить Filter Function
Откройте Open WebUI:
Код:
Рабочая область → Функции
Создайте новую функцию и вставьте код.
После сохранения обязательно:
1. Включите функцию.
2. Включите её глобально либо назначьте конкретной модели.
3. Откройте параметры Valves.
4. Установите необходимые значения.
Сам факт появления в журнале строки:
Код:
Loaded module: function_zer0kernel_context_window
означает только успешную загрузку Python-модуля.
Это ещё не означает, что фильтр применяется к запросам.
Если функция должна работать со всеми моделями, включите:
Код:
Active: true
Global: true
Обратите внимание на Global: true
---
# Рекомендуемые настройки
Для модели:
Код:
max_model_len = 100000
max_tokens = 20000
можно начать с таких параметров:
Код:
Priority: 1000
Max Messages: 6
Max Input Tokens: 30000
Chars Per Token: 2.5
Message Overhead Tokens: 16
Image Token Reserve: 1500
Keep Current User Message: true
Reject Oversized Request: true
Debug: true
Debug Preview Chars: 0
---
# Как работает
Max Messages = 6Системный промпт в этот лимит не входит.
Допустим, история выглядит так:
Код:
system
user-1
assistant-1
user-2
assistant-2
user-3
assistant-3
user-4
Фильтр берёт последние шесть обычных сообщений:
Код:
assistant-1
user-2
assistant-2
user-3
assistant-3
user-4
Но первое сообщение является ответом без соответствующего вопроса. Поэтому оно удаляется.
Модель получает:
Код:
system
user-2
assistant-2
user-3
assistant-3
user-4
То есть:
- системный промпт;
- две предыдущие пары;
- текущий вопрос.
6 — это максимальное количество сообщений, а не обязательное.Если токеновый бюджет превышен, сообщений может остаться меньше.
---
# Как работает
Max Input Tokens = 30000После ограничения количества сообщений функция оценивает размер контекста.
Например:
Код:
system 3000 токенов
user-2 7000 токенов
assistant-2 5000 токенов
user-3 8000 токенов
assistant-3 4000 токенов
текущий user 6000 токенов
------------------------------------
всего 33000 токенов
Установлен лимит:
Код:
Max Input Tokens = 30000
Контекст превышает бюджет:
Код:
33000 > 30000
Фильтр удаляет самый старый полный ход:
Код:
user-2
assistant-2
После удаления остаётся:
Код:
system 3000 токенов
user-3 8000 токенов
assistant-3 4000 токенов
текущий user 6000 токенов
------------------------------------
всего 21000 токенов
Теперь контекст помещается в установленный бюджет.
---
# Почему нужны два ограничения
Одного
Max Messages недостаточно.Шесть коротких сообщений могут занимать:
Код:
1000 токенов
Но шесть сообщений с исходным кодом и журналами могут занимать:
Код:
50000 токенов
Поэтому используются одновременно:
Код:
Max Messages = 6
Max Input Tokens = 30000
Первый параметр ограничивает количество сообщений.
Второй параметр ограничивает их приблизительный общий размер.
---
# Что происходит с одним огромным запросом
Допустим:
Код:
system prompt 3000 токенов
текущий вопрос 32000 токенов
--------------------------------
всего 35000 токенов
Старой истории нет. Удалять больше нечего.
При настройках:
Код:
Keep Current User Message = true
Reject Oversized Request = true
фильтр отклонит запрос.
Пользователь получит ошибку о превышении внутреннего бюджета, а запрос не будет передан в vLLM.
Если отключить:
Код:
Reject Oversized Request = false
фильтр сохранит текущий запрос и передаст его дальше, несмотря на превышение.
В таком случае vLLM снова может вернуть:
Код:
This model's maximum context length is...
Для строгой защиты рекомендуется:
Код:
Reject Oversized Request = true
---
# Как приблизительно считаются токены
Функция не использует точный токенизатор Qwen.
Она применяет формулу:
Код:
tokens ≈ characters / chars_per_token
При:
Код:
Chars Per Token = 2.5
получаем:
Код:
25000 символов ≈ 10000 токенов
50000 символов ≈ 20000 токенов
75000 символов ≈ 30000 токенов
Чем меньше значение
Chars Per Token, тем раньше срабатывает ограничение.Более строгая настройка:
Код:
Chars Per Token = 2.2
Менее строгая:
Код:
Chars Per Token = 3.0
Для смеси русского текста, исходного кода и JSON разумный диапазон:
Код:
2.3–2.5
---
# Учитывается ли системный промпт
Системный промпт не входит в:
Код:
Max Messages
Но входит в:
Код:
Max Input Tokens
Например:
Код:
Max Input Tokens = 30000
Системный промпт = 5000
На историю и текущий вопрос остаётся примерно:
Код:
30000 - 5000 = 25000 токенов
Системный промпт фильтр не удаляет.
Если сам системный промпт превышает бюджет, запрос будет отклонён даже при пустой истории.
---
# Работа с файлами
Важно понимать, что содержимое файлов может обрабатываться Open WebUI отдельно от обычной истории сообщений.
Если файл используется в режиме Full Context, он может вставляться в запрос целиком.
Поэтому для больших файлов рекомендуется:
Код:
Focused Retrieval
При повторной загрузке нескольких файлов следует убедиться, что Open WebUI не передаёт все предыдущие источники в каждом новом запросе.
Практичная стратегия:
Код:
1. В текущем запросе использовать все специально прикреплённые файлы.
2. В следующем запросе без вложений не передавать прошлые файлы повторно.
3. Для больших документов использовать Focused Retrieval.
4. Ограничить Top K.
5. Не использовать Full Context для книг и крупных PDF без необходимости.
Если модель уже пересказала содержимое файла в предыдущем ответе, этот пересказ становится обычной частью истории и может сохраняться до срабатывания
Max Messages или Max Input Tokens.---
# Почему
Max Input Tokens = 30000 безопасенПри:
Код:
max_model_len = 100000
max_tokens = 20000
максимальный вход vLLM равен:
Код:
80000 токенов
Фильтр ограничивает контролируемую часть входа до:
Код:
30000 токенов
Остаётся запас:
Код:
80000 - 30000 = 50000 токенов
Он может использоваться для:
- погрешности приблизительного подсчёта;
- шаблона чата;
- содержимого файлов;
- RAG-фрагментов;
- схем инструментов;
- внутренних инструкций;
- данных других расширений;
- служебных токенов модели.
Значение
30000 является консервативным.Для обычного чата без больших файлов можно использовать:
Код:
40000–50000
Но сначала лучше провести тестирование.
---
# Проверка через Docker-журнал
Для просмотра работы фильтра выполните:
Bash:
docker logs -f --tail=100 zer0kernel-open-webui 2>&1 |
grep --line-buffered ZK_CONTEXT
Вместо zer0kernel-open-webui вставьте своё имя контейнера.
Либо команду...
Пример строки:
Код:
[ZK_CONTEXT]
raw_messages=10,
valid_messages=10,
leading_control=1,
conversation=9->3,
dropped_by_message_limit=4,
dropped_turns_by_token_limit=1,
tokens~=47000->18200,
budget=30000,
over_budget=false
Расшифровка:
Код:
conversation=9->3
Было девять обычных сообщений, модели отправлено три.
Код:
dropped_by_message_limit=4
Четыре сообщения были удалены из-за
Max Messages.
Код:
dropped_turns_by_token_limit=1
После этого удалён ещё один полный диалоговый ход из-за токенового бюджета.
Код:
tokens~=47000->18200
До обработки было приблизительно 47 000 токенов, после обработки осталось около 18 200.
Код:
budget=30000
Установленный бюджет равен 30 000 токенов.
Код:
over_budget=false
Итоговый контекст помещается в заданный лимит.
---
# Тест обрезки истории
Создайте новый чат и последовательно отправьте несколько крупных запросов.
Первый запрос:
Код:
Напиши подробный обзор архитектуры FastAPI-сервиса объёмом около 3000 слов.
Второй:
Код:
Добавь PostgreSQL, Redis, очередь задач и распределённые блокировки.
Сохрани подробность ответа.
Третий:
Код:
Добавь структуру проекта и полные примеры основных файлов.
Четвёртый:
Код:
Добавь Docker Compose, Nginx и систему мониторинга.
После нескольких запросов журнал должен показать обрезку:
Код:
conversation=9->5
или:
Код:
dropped_turns_by_token_limit=1
При этом запросы должны продолжать выполняться без ошибки
maximum context length.---
# Тест с маркерами
Для проверки количества сообщений можно использовать уникальные строки.
Отправьте по очереди:
Код:
Запомни строку CTX_M1_111. Ответь только ACK.
Код:
Запомни строку CTX_M2_222. Ответь только ACK.
Продолжите:
Код:
CTX_M3_333
CTX_M4_444
CTX_M5_555
CTX_M6_666
CTX_M7_777
После этого отправьте:
Код:
Выведи только строки с префиксом CTX_, которые дословно присутствуют
в переданном тебе контексте. Не угадывай удалённые строки.
При
Max Messages = 6 модель должна видеть только несколько последних маркеров.Окончательный результат лучше проверять по журналу
ZK_CONTEXT, а не только по ответу модели.---
# Частые ошибки настройки
## Функция создана, но не запускается
Проверьте:
Код:
Рабочая область → Функции
Функция должна быть:
Код:
Active = true
А также либо:
Код:
Global = true
либо назначена конкретной модели.
---
## В журнале нет
ZK_CONTEXTЕсли отсутствуют строки:
Код:
[ZK_CONTEXT]
фильтр не применяется к запросу.
Возможные причины:
- функция выключена;
- глобальный режим не включён;
- функция не назначена модели;
- запрос проходит через другую модель;
- используется другой Open WebUI-контейнер;
- код не был сохранён.
---
## Установлен
Priority = 0Фильтры с меньшим приоритетом выполняются раньше.
Если ограничитель должен работать после других пользовательских фильтров, установите:
Код:
Priority = 1000
Это не гарантирует, что он будет последним внутренним обработчиком Open WebUI, но позволяет запускать его после большинства других Filter Functions.
---
## Отключён
Reject Oversized RequestПри:
Код:
Keep Current User Message = true
Reject Oversized Request = false
один огромный текущий вопрос может пройти дальше, несмотря на превышение внутреннего бюджета.
Для строгой защиты:
Код:
Reject Oversized Request = true
---
## Используется Full Context для крупных файлов
Если файл занимает десятки тысяч токенов, переключите его в:
Код:
Focused Retrieval
И уменьшите:
Код:
Top K
Например:
Код:
Top K = 3
---
## Слишком большой
max_tokensЗначение
max_tokens=20000 резервирует место под очень длинный ответ, даже если модель фактически ответит несколькими предложениями.Для обычного программирования можно использовать:
Код:
8000–12000
Для крупных статей и больших файлов кода:
Код:
12000–20000
---
# Оптимальная конфигурация для Qwen Coder
Для модели с окном:
Код:
max_model_len = 100000
и максимальным ответом:
Код:
max_tokens = 20000
безопасная начальная конфигурация:
Код:
Priority: 1000
Max Messages: 6
Max Input Tokens: 30000
Chars Per Token: 2.5
Message Overhead Tokens: 16
Keep Current User Message: true
Reject Oversized Request: true
Debug: true
Для менее строгого режима:
Код:
Max Input Tokens = 40000–50000
Но перед увеличением следует проверить:
- длинные ответы;
- большие исходные файлы;
- несколько загруженных документов;
- RAG;
- веб-поиск;
- инструменты;
- размер системного промпта.
---
# FAQ
## Почему ошибка показывает именно 80 001 входной токен?
Потому что:
Код:
100000 - 20000 = 80000
Вход до 80 000 токенов допустим.
Первое недопустимое значение:
Код:
80001
---
## Удаляет ли фильтр сообщения из интерфейса?
Нет.
Сообщения остаются в чате и базе Open WebUI.
Фильтр изменяет только временный запрос, который отправляется модели.
---
## Будет ли модель помнить старые сообщения?
Только пока они находятся в передаваемом контексте.
Если старый ход был удалён фильтром, модель больше его не видит.
Однако часть информации может сохраниться в более новых сообщениях, если пользователь или модель повторили её.
---
## Что важнее:
Max Messages или Max Input Tokens?Нужны оба параметра.
Max Messages защищает от слишком большого количества сообщений.Max Input Tokens защищает от нескольких огромных сообщений.Шесть коротких сообщений и шесть больших ответов с кодом — это совершенно разные объёмы контекста.
---
## Можно ли оставить только последний вопрос?
Можно, но качество длительной работы снизится.
Модель начнёт забывать:
- архитектуру проекта;
- имена функций;
- названия классов;
- используемые библиотеки;
- предыдущие исправления;
- требования пользователя.
Практичный компромисс:
Код:
системный промпт
+
последние 4–6 сообщений
+
токеновый бюджет
---
## Почему не использовать только Context Compaction?
Автоматическое сжатие может зависеть от:
- версии Open WebUI;
- режима чата;
- конкретной модели;
- настроек;
- доступности вспомогательной модели;
- работы приватного чата.
Код:
контекст помещается
или старые ходы удаляются
или слишком большой запрос отклоняется
---
# Заключение
Ошибка:
Код:
This model's maximum context length is...
не означает, что vLLM или Qwen неисправны.
Она означает, что сумма:
Код:
история
+ системный промпт
+ текущий вопрос
+ файлы
+ RAG
+ инструменты
+ максимальный ответ
перестала помещаться в заданное контекстное окно.
Надёжная защита состоит из нескольких уровней:
Код:
1. Разумный max_tokens.
2. Ограничение количества сообщений.
3. Ограничение входного токенового бюджета.
4. Удаление старых полных диалоговых ходов.
5. Focused Retrieval для больших документов.
6. Контроль повторного использования файлов.
7. Отклонение одиночных слишком больших запросов.
8. Проверка работы через Docker-журнал.
Для модели с окном 100 000 токенов и
max_tokens=20000 можно начать с:
Код:
Max Messages = 6
Max Input Tokens = 30000
Chars Per Token = 2.5
Priority = 1000
Reject Oversized Request = true
Такая конфигурация не просто скрывает ошибку, а устраняет основную причину — бесконтрольное накопление контекста в Open WebUI.
Последнее редактирование:
