Вирішення проблемПосібник
400 після виклику інструмента: надсилайте reasoning_content назад
Чому другий запит циклу виклику інструментів повертає 400 на моделі з міркуванням, виправлення на Python, що додає повне повідомлення, і коли це можна пропустити.

На цій сторінці
Ваш цикл агента працює на першому запиті й падає на другому з 400. На моделі з міркуванням одна задокументована причина — відсутнє поле: повідомлення асистента, яке ви надсилаєте назад, втратило свій reasoning_. Якщо причина саме в цьому, додавання всього повідомлення точно в тому вигляді, як його повернув API, виправляє ситуацію.
Посібник DeepSeek з режиму міркування (документація, перевірено 8 листопада 2026 року) формулює правило одним реченням: коли в запиті є параметр tools, reasoning_ потрібно передавати назад повністю в усіх наступних запитах, включно з ходами без виклику інструмента. Інакше API повертає 400. На тій самій сторінці сказано, що міркування ввімкнено за замовчуванням, з рівнем зусиль high. Тож цикл, написаний для моделі без міркування, може зламатися без жодної зміни рядка.
Що відбувається між першим і другим запитом#
У режимі міркування ланцюжок думок повертається в reasoning_ — полі на тому самому рівні, що й content. Уявіть цикл:
- Ви надсилаєте запитання та список
tools. - Модель відповідає
tool_calls(іreasoning_поруч із ними).content - Ви запускаєте інструмент і надсилаєте все назад: історію, повідомлення асистента та повідомлення
toolз результатом.
400 виникає на запиті з кроку 3, якщо повідомлення асистента в ньому не має reasoning_.

Сторінка DeepSeek документує 400 лише для цього випадку з викликом інструмента й не наводить тексту помилки, тож перевіряючи, чи маєте ви цю проблему, дивіться на HTTP-статус, а не на конкретне повідомлення.
Виправлення: додавайте повідомлення як повернуто#
Найбезпечніший крок — взагалі не перебудовувати повідомлення асистента. Візьміть його з відповіді й додайте цілком. Ось повний цикл з OpenAI Python SDK, націленим на CheapTokens. Інструмент погоди — заглушка, тож його можна запускати як є.
import json, os
from openai import OpenAI
client = OpenAI(
base_url="https://api.cheaptokens.dev/v1",
api_key=os.environ["CHEAPTOKENS_API_KEY"],
)
MODEL = "deepseek-ai/DeepSeek-V4-Flash-0731"
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Current weather for a city",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
},
}]
def get_weather(city):
return {"city": city, "temp_c": 7} # stub
messages = [{"role": "user", "content": "Do I need a jacket in Oslo today?"}]
for _ in range(5): # hard step limit
resp = client.chat.completions.create(model=MODEL, messages=messages, tools=tools)
msg = resp.choices[0].message
messages.append(msg.model_dump(exclude_none=True)) # the whole message
if not msg.tool_calls:
print(msg.content)
break
for call in msg.tool_calls:
args = json.loads(call.function.arguments)
messages.append({
"role": "tool",
"tool_call_id": call.id,
"content": json.dumps(get_weather(**args)),
})Єдиний рядок, що має значення, — messages.. SDK зберігає поля, про які не знає, тож усе, що API поклав на повідомлення, включно з reasoning_, подорожує назад разом із ним. Фінальна відповідь також додається, і саме цього вимагає правило: після першого виклику інструмента кожен наступний запит несе міркування попередніх ходів асистента.
Сама механіка виклику інструментів (схема, tool_call_id, розбір аргументів) описана в Function calling in Python with an OpenAI-compatible API. Базовий URL, ключ і ідентифікатор моделі є на сторінці документації CheapTokens; модель, використана тут, описана на сторінці DeepSeek: контекст 380K і до 32 768 вихідних токенів на відповідь, що залишає місце для довгого міркування плюс відповіді.
Поширений винуватець: повідомлення, перебудоване вручну#
Поширений спосіб втратити поле — код, який не додає об'єкт відповіді, а будує новий dict. Це виглядає нешкідливо: role, content, tool calls. Фреймворки та обгортки роблять те саме, коли зберігають історію у власному форматі. Єдине поле, про яке вони ніколи не чули, — це те, що зникає.

Порівняйте
Перебудоване вручну проти доданого цілком
Перебудоване вручну (ламається)
messages.append({
"role": "assistant",
"content": msg.content,
"tool_calls": [c.model_dump() for c in msg.tool_calls],
})Додане цілком (працює)
messages.append(msg.model_dump(exclude_none=True))Якщо ви використовуєте фреймворк і не можете торкнутися того рядка, перевірте, як він зберігає ходи асистента. Історія, що проходить через серіалізатор, який зберігає лише content і tool_calls, має той самий баг, просто на один рівень нижче.
Коли це не потрібно#
Не кожен виклик моделі з міркуванням потребує цієї додаткової обережності. Для багатоходового чату без інструментів посібник DeepSeek каже, що reasoning_ не потрібно передавати назад; якщо ви все одно надсилаєте його, він ігнорується, і попередні міркування не додаються до контексту. Тож звичайний чат може й далі відкидати його, і нічого не ламається.
Оберіть свій випадок
Який випадок ваш?
Виберіть варіант, щоб побачити відповідь
У запиті є tools, модель міркує
Додавайте все повідомлення асистента щоразу, як на ходах з інструментами, так і на фінальних відповідях.
Звичайний чат, без параметра tools
Виправлення не потрібне. Можете відкидати reasoning_ з історії або надсилати його; він ігнорується.
Фреймворк будує історію за вас
Знайдіть, де він серіалізує повідомлення асистента, і переконайтеся, що reasoning_ зберігається. Якщо ні, викликайте API власним циклом для частини з інструментами.
Побічний ефект, який варто мати на увазі: історія, яку ви надсилаєте повторно, є вхідними даними в кожному запиті, тож довгі міркування в довгому циклі накопичуються. Як рахуються токени міркування, описано в Reasoning effort and thinking tokens, а що робити, коли запит перевищує ліміти моделі, — у Request too long: 400 vs 413.
Якщо все ще повертає 400#
Пройдіть це по порядку:
- Надрукуйте
messagesбезпосередньо перед викликом, що падає. Чи має кожне повідомлення асистента, що прийшло від моделі,reasoning_, якщо відповідь його мала?content - Чи перебудовуєте ви повідомлення десь: у хелпері, класі історії,
json.dumpsі назад із фіксованим списком ключів? - Чи присутнє повідомлення
toolдля кожногоtool_call_id, з тим самим id? - Чи це справді довжина? Запит понад ліміт контексту — це інший 400, описаний у статті вище.
Швидка перевірка
Що ви додаєте після того, як модель просить інструмент?
Показати відповідь
Відповідь: Усе повідомлення асистента як повернуто, включно з reasoning_content
На tool_calls відповідають ваші повідомлення інструментів, і коли в запиті є інструменти, міркування має їхати разом із ними.
Ось і все виправлення: одне додавання, жодної перебудови. Додайте ліміт кроків із прикладу до будь-якого циклу, який випускаєте, щоб поганий хід коштував п'яти запитів, а не п'ятисот.



