Перевод большого гайда от Open AI: как писать промпты для GPT-4.1

14 апреля компания выпустила большой гайд по промптам к новой модели GPT-4.1. Мы его перевели и адаптировали, приятного чтения 🙃

картинка для привлечения внимания
картинка для привлечения внимания

Общие рекомендации. Контекстные примеры, подробные конкретные запросы, подталкивание модели к тому, чтобы она сама выстраивала логические цепочки, — всё это можно и нужно использовать с GPT-4.1, как и раньше.

Но прошлые модели свободнее интерпретировали запросы. А GPT-4.1 обучили следовать им более буквально. Она чётко реагирует на инструкции и легко управляется. Это удобно, если точно знаете, какой результат вам нужен. И если модель генерирует что-то не то, можно поправить её одним недвусмысленным предложением.

В гайде даём конкретные рекомендации и примеры. Только не забывайте, что работа с ИИ — это процесс, и универсальных рецептов нет. Корректируйте формулировки под себя.

1. Автономное решение задач

GPT-4.1 отлично подходит для самостоятельных рабочих процессов, в которых нейросеть сама идёт к нужному результату.

Чтобы обучить модель, разработчики использовали множество примеров автономного решения задач. Поэтому GPT-4.1 превосходит другие неспециализированные ИИ. Например, она корректно решает 55% задач из теста SWE-bench Verified (теста на исправление ошибок в коде).

Эффективная система промптов

Чтобы по максимуму задействовать автономные возможности GPT-4.1, включите в промпт три элемента.

Ты — автономный помощник. Продолжай работать, пока задача пользователя не будет полностью решена. Не завершай диалог, пока не убедишься, что проблема решена.

2. Функция вызова инструментов (Tool-calling). Так модель активнее применяет готовые инструменты, что уменьшает вероятность «галлюцинаций» и случайных догадок. Пример промпта:

Если не уверен в содержимом файла или структуре кода в запросе пользователя — используй инструменты, чтобы прочитать файлы и собрать нужную информацию. Не пытайся угадать или придумать ответ.

3. Логические цепочки (Planning). При включении этой функции модель заранее продумывает и описывает каждый шаг работы с инструментами, а не автоматически выполняет их друг за другом. Пример промпта:

Ты ДОЛЖЕН тщательно планировать каждый следующий шаг перед использованием функции и подробно анализировать результаты предыдущих операций. НЕ выполняй задачу только через цепочку автоматических действий — это ухудшит твои аналитические способности и качество решения.

В тестах с этими тремя запросами GPT-4.1 повысила эффективность почти на 20%. Если начинать работу с них, модель станет более «осознанным» участником процесса.

Вызов инструментов (Tool Calls)

Если сравнивать с предыдущими моделями, GPT-4.1 лучше натренирована использовать инструменты. Но не забывайте о четырёх правилах:

  1. Укажите нужные тулы в поле «Инструменты». Это минимизирует ошибки.
  2. Опишите их конкретно и чётко.
  3. Если ваш инструмент сложный, создайте раздел #Примеры в промпте и разместите референсы там. Не добавляйте их в #Описание. Примеры помогут нейросети сориентироваться, когда использовать инструмент.
  4. Если потребуется основа для новых инструментов, воспользуйтесь функцией «Сгенерировать что угодно» в Prompt Playground.

Логические цепочки промптов

Между вызовами инструментов можно организовать логическую связь. Тогда нейросеть будет опираться не только на задачу, но и на свои «размышления». Проанализирует запрос и логически свяжет его с предыдущим.

Важно: по умолчанию GPT-4.1 не планирует и не выстраивает логические связи. Но чёткий промпт, как пример в пункте «Логические цепочки», может побудить её «размышлять».

Эксперименты с тестом SWE-bench Verified показали: явная просьба пользователя выстраивать логическую цепочку повышает процент успешных решений на 4%.

Пример промпта, проверенного на SWE-bench Verified

Он оптимизирован для программирования, но его можно изменить для решения других задач.

from openai import OpenAI import os client = OpenAI( api_key=os.environ.get( "OPENAI_API_KEY", "<your OpenAI API key if not set as env var>" ) ) SYS_PROMPT_SWEBENCH = """ Тебе будет поставлена задача исправить проблему в open-source репозитории. Твои рассуждения должны быть детальными, даже если они будут очень длинными. Можно размышлять шаг за шагом до и после каждого действия. Ты ДОЛЖЕН итеративно работать над решением, пока проблема не будет решена. У тебе уже есть всё необходимое для решения этой проблемы в папке /testbed, даже без подключения к интернету. Реши задачу полностью автономно, прежде чем вернуться ко мне. Заверши сеанс только когда будешь уверен, что проблема решена. Анализируй проблему шаг за шагом и обязательно проверяй корректность изменений. НИКОГДА не завершай сеанс, не решив проблему. Когда ты говоришь, что собираешься выполнить вызов инструмента, убедись, что ДЕЙСТВИТЕЛЬНО выполняешь его, а не завершаешь сеанс. ЭТУ ПРОБЛЕМУ МОЖНО РЕШИТЬ БЕЗ ИНТЕРНЕТА. Не торопись и тщательно продумывай каждый шаг — проверяй своё решение и учитывай пограничные случаи, особенно для изменений, которые вносишь. Твоё решение должно быть идеальным. Если это не так — продолжай работать над ним. В конце ты должен тщательно протестировать свой код с помощью предоставленных инструментов, сделать это многократно, чтобы выявить все пограничные случаи. Если решение не достаточно надежно — продолжай итерации, пока не достигнешь совершенства. Недостаточное тестирование кода — ГЛАВНАЯ причина неудач в таких задачах; убедись, что ты обработал все пограничные случаи и запустил существующие тесты, если они предоставлены. Ты ДОЛЖЕН тщательно планировать каждый вызов функции и анализировать результаты предыдущих вызовов. НЕ выполняй весь процесс только через вызовы функций, так как это ухудшит твою способность решать проблему и мыслить аналитически. # Рабочий процесс ## Стратегия высокоуровневого решения 1. Глубокое понимание проблемы. Внимательно прочитай описание и критически осмысли требования. 2. Исследование базы кода. Изучи соответствующие файлы, найди ключевые функции и собери контекст. 3. Разработка детального плана. Разбей исправление на управляемые последовательные шаги. 4. Постепенные правки кода. Вноси небольшие, тестируемые изменения в код. 5. Отладка. При необходимости используй методы отладки для изоляции и решения проблем. 6. Тестирование. Проводи его часто. Запускай тесты после каждого изменения для проверки корректности. 7. Финальная проверка. Повторяй тесты, пока не выполнишь все необходимые и не будет устранена первопричина проблемы. 8. Финальный анализ и дополнительное тестирование. После прохождения тестов подумай об исходной цели, напиши дополнительные тесты для гарантии корректности и помни, что есть скрытые тесты, которые также должны быть пройдены. Обратись к детальным разделам ниже, чтобы получить дополнительную информацию по каждому шагу. ## 1. Глубокое понимание проблемы - Внимательно прочитай описание проблемы и тщательно продумай план решения перед написанием кода. ## 2. Исследование базы кода - Изучи соответствующие файлы и директории - Найди ключевые функции, классы или переменные, связанные с проблемой - Прочитай и пойми соответствующие фрагменты кода - Определи первопричину проблемы - Постоянно проверяй и обновляй своё понимание по мере сбора контекста ## 3. Разработка детального плана - Составь конкретную, простую и проверяемую последовательность шагов для исправления - Разбей исправление на небольшие пошаговые изменения ## 4. Постепенные правки кода - Перед редактированием всегда читай содержимое соответствующих файлов для полного понимания контекста - Если патч не применён корректно, попытайся применить его повторно - Вноси небольшие, тестируемые, последовательные изменения, логически следующие из твоего исследования и плана ## 5. Отладка - Вноси изменения в код, только если уверен, что они решат проблему - При отладке старайся определить первопричину, а не симптомы - Отлаживай столько, сколько нужно для выявления первопричины и решения - Используй print-выражения, логи или временный код для проверки состояния программы - Для проверки гипотез можно добавлять тестовые выражения или функции - Пересматривай свои предположения при неожиданном поведении ## 6. Тестирование - Как можно чаще запускай тесты с помощью `!python3 run_tests.py` (или аналога) - После каждого изменения проверяй корректность запуском соответствующих тестов - При падении тестов анализируй причины и дорабатывай патч - При необходимости напиши дополнительные тесты для проверки важных сценариев - Убедись, что все тесты пройдены перед финальной проверкой ## 7. Финальная проверка - Убедись, что первопричина проблемы устранена - Проверь решение на логическую корректность и надёжность - Повторяй, пока не будешь полностью уверен в завершении исправления ## 8. Финальный анализ и дополнительное тестирование - Тщательно проанализируй исходную цель пользователя и постановку задачи - Подумай о потенциальных граничных случаях, не покрытых существующими тестами - Напиши дополнительные тесты для полной валидации корректности решения - Запусти эти новые тесты и убедись, что они пройдены успешно - Помни, что есть дополнительные скрытые тесты, которые также должны быть пройдены - Не считай задачу завершенной только потому, что видимые тесты пройдены; продолжай улучшения, пока не будешь уверен в надежности и полноте исправления PYTHON_TOOL_DESCRIPTION = """This function is used to execute Python code or terminal commands in a stateful Jupyter notebook environment. python will respond with the output of the execution or time out after 60.0 seconds. Internet access for this session is disabled. Do not make external web requests or API calls as they will fail. Just as in a Jupyter notebook, you may also execute terminal commands by calling this function with a terminal command, prefaced with an exclamation mark. In addition, for the purposes of this task, you can call this function with an `apply_patch` command as input. `apply_patch` effectively allows you to execute a diff/patch against a file, but the format of the diff specification is unique to this task, so pay careful attention to these instructions. To use the `apply_patch` command, you should pass a message of the following structure as "input": %%bash apply_patch <<"EOF" *** Begin Patch [YOUR_PATCH] *** End Patch EOF Where [YOUR_PATCH] is the actual content of your patch, specified in the following V4A diff format. *** [ACTION] File: [path/to/file] -> ACTION can be one of Add, Update, or Delete. For each snippet of code that needs to be changed, repeat the following: [context_before] -> See below for further instructions on context. - [old_code] -> Precede the old code with a minus sign. + [new_code] -> Precede the new, replacement code with a plus sign. [context_after] -> See below for further instructions on context. For instructions on [context_before] and [context_after]: - By default, show 3 lines of code immediately above and 3 lines immediately below each change. If a change is within 3 lines of a previous change, do NOT duplicate the first change's [context_after] lines in the second change's [context_before] lines. - If 3 lines of context is insufficient to uniquely identify the snippet of code within the file, use the @@ operator to indicate the class or function to which the snippet belongs. For instance, we might have: @@ class BaseClass [3 lines of pre-context] - [old_code] + [new_code] [3 lines of post-context] - If a code block is repeated so many times in a class or function such that even a single @@ statement and 3 lines of context cannot uniquely identify the snippet of code, you can use multiple `@@` statements to jump to the right context. For instance: @@ class BaseClass @@ def method(): [3 lines of pre-context] - [old_code] + [new_code] [3 lines of post-context] Note, then, that we do not use line numbers in this diff format, as the context is enough to uniquely identify code. An example of a message that you might pass as "input" to this function, in order to apply a patch, is shown below. %%bash apply_patch <<"EOF" *** Begin Patch *** Update File: pygorithm/searching/binary_search.py @@ class BaseClass @@ def search(): - pass + raise NotImplementedError() @@ class Subclass @@ def search(): - pass + raise NotImplementedError() *** End Patch EOF File references can only be relative, NEVER ABSOLUTE. After the apply_patch command is run, python will always say "Done!", regardless of whether the patch was successfully applied or not. However, you can determine if there are issue and errors by looking at any warnings or logging lines printed BEFORE the "Done!" is output. """ python_bash_patch_tool = { "type": "function", "name": "python", "description": PYTHON_TOOL_DESCRIPTION, "parameters": { "type": "object", "strict": True, "properties": { "input": { "type": "string", "description": " The Python code, terminal command (prefaced by exclamation mark), or apply_patch command that you wish to execute.", } }, "required": ["input"], }, } # Additional harness setup: # - Add your repo to /testbed # - Add your issue to the first user message # - Note: Even though we used a single tool for python, bash, and apply_patch, we generally recommend defining more granular tools that are focused on a single function response = client.responses.create( instructions=SYS_PROMPT_SWEBENCH, model="gpt-4.1-2025-04-14", tools=[python_bash_patch_tool], input=f"Please answer the following question:\nBug: Typerror..." ) response.to_dict()["output"]

2. Долгое сохранение контекста

GPT-4.1 поддерживает контекст до 1 миллиона токенов и эффективно справляется с большими задачами. Он может:

  • анализировать структурированные документы,
  • проводить повторную сортировку,
  • выбирать релевантную информацию,
  • размышлять с использованием контекста.

Оптимальный размер контекста

Модель отлично находит нужную информацию в длинных текстах и эффективно анализирует смешанный контент, где есть и полезные, и бесполезные данные.

Но эффективность может падать при анализе множества элементов одновременно или подготовке сложных выводов, учитывающих весь контекст.

Настройте зависимость от контекста

Перед тем как составлять запрос, подумайте, какие факты потребуются. Должна ли нейросеть задействовать собственные знания или только представленные в контексте данные? Уточните это в промпте:

# Инструкции // для работы с заданным контекстом - Для ответа на вопрос используй факты ТОЛЬКО из предоставленного контекста. Если в этом контексте решения нет, ты ДОЛЖЕН ответить «У меня нет необходимой информации для ответа на этот вопрос», даже если пользователь настаивает на получении ответа. // для работы с заданным контекстом и базовыми знаниями - По умолчанию используй предоставленный контекст для ответа на вопрос, но если требуются базовые знания и ты уверен в ответе, используй свои собственные знания.

Оптимальное место для инструкций

Положение инструкций в промпте влияет на производительность нейросети, особенно если контекст длинный.

Тесты показали, что размещать инструкции стоит в начале и в конце контекста. Если же выбирать из этого что-то одно, то эффективнее давать инструкции вверху.

3. Цепочка рассуждений (Chain of Thought)

Повторим: GPT-4.1 — не модель логического мышления, но пользователь может побудить её строить цепочки рассуждений. Это эффективно, когда модели нужно разбить задачи на более управляемые части. Так качество результатов повысится. Правда, при этом модель будет «думать» дольше.

Можно использовать в конце запроса такую инструкцию:

Сначала тщательно пошагово продумай, какие файлы необходимы для ответа на запрос. Затем выведи НАЗВАНИЕ и ID каждого файла. После этого отформатируй ID в виде списка.

Чтобы улучшить цепочку рассуждений, нужно проанализировать ошибки в ответе. Более чёткие инструкции помогут устранить проблемы в логике.

Чаще всего нейросеть ошибается из-за неправильного понимания намерений пользователя, недостаточного сбора или анализа контекста, неполного или неверного пошагового рассуждения. На эти моменты стоит обращать особое внимание и корректировать их детализированными инструкциями. Вот как их можно сформулировать:

# Стратегия анализа запроса 1. Анализ запроса. Разбей и проанализируй запрос, пока не будешь уверен в его понимании. Используй предоставленный контекст для уточнения неоднозначной информации. 2. Анализ контекста. Тщательно отбери и проанализируй набор потенциально релевантных файлов. При анализе каждого документа оценивай степень его релевантности запросу: [высокая, средняя, низкая, отсутствует]. Оптимизируй набор файлов для полноты охвата. Допускается включать некоторые нерелевантные документы, но правильные документы обязательно должны быть в этом списке, иначе итоговый ответ будет неверным. 3. Синтез. Обобщи, какие документы наиболее релевантны и почему, включая все документы с оценкой релевантности «средняя»‎ или выше. # Вопрос пользователя {user_question} # Внешний контекст {external_context} Сначала тщательно продумай пошагово, какие документы нужны для ответа на запрос, строго следуя представленной Стратегии анализа. Затем выведи НАЗВАНИЕ и ID каждого документа. После этого отформатируй ID в виде списка.

4. Следование инструкциям

GPT-4.1 умеет чётко следовать указаниям. Это позволяет пользователям контролировать результат без дополнительных усилий.

Но неявные запросы теперь действовать не будут. Промпты, оптимизированные для других GPT, могут не так эффективно работать в GPT-4.1. Ей нужно чётко указать, что делать и чего не делать. Модель понимает всё буквально.

Рекомендации по работе

Собрали советы для эффективных и безошибочных инструкций.

  1. Начинайте промпты с разделов: «Правила формирования ответа» или «Инструкции». Обозначьте в них общие рекомендации для модели.
  2. Если хотите изменить поведение модели точечно, добавьте раздел для дополнительных сведений.
  3. Чтобы модель выполняла определённые шаги в процессе работы, добавьте их чёткий список.
  4. Если нейросеть всё ещё ведёт себя не так, как вы ожидали, то:
    а) Проверьте, нет ли противоречий в ваших указаниях. Если есть, GPT-4.1 следует тому, которое ближе к концу запроса.
    б) Добавьте примеры желаемого поведения.
    в) Поэкспериментируйте с дополнительными элементами: капсом или системой вознаграждения для модели — хотя обычно в этом нет необходимости.

Разработчикам полезно использовать IDE с ИИ-поддержкой для итеративной разработки промптов, чтобы проверять их согласованность, добавлять примеры и комплексно обновлять инструкции.

Распространённые причины ошибок

Эти ошибки свойственны не только GPT-4.1, но их важно упомянуть.

  • Слишком строгие инструкции могут привести к ошибке.
    Например, вы попросите:
    Вызови инструмент перед ответом
    Тогда модель может ошибочно использовать нулевые значения или «галлюцинировать». Поэтому добавьте:
    При недостатке данных запроси их у пользователя
    Это предотвратит ошибку.
  • Если вы приведёте примеры фраз, все ответы модели могут стать похожими на них и будут выглядеть однообразно. Укажите, что она должна менять формулировки при необходимости.
  • Без конкретных инструкций некоторые модели ищут дополнительную информацию для объяснения своих решений или дают больше ответов, чем надо. Чётко укажите, в какой информации вы нуждаетесь и в каком объёме.

Пример промпта: Служба поддержки клиентов

Этот промпт включает в себя все вышеупомянутые правила. Здесь указана специфика запроса и приведены примеры. В дополнительных разделах запрос детализируется так, чтобы модель лучше его поняла.

from openai import OpenAI import os client = OpenAI( api_key=os.environ.get( "OPENAI_API_KEY", "<ваш API-ключ OpenAI, если не задан как переменная окружения>" ) ) SYS_PROMPT_CUSTOMER_SERVICE = """Ты — полезный агент службы поддержки NewTelco, который помогает клиенту эффективно решить его запрос, строго следуя инструкциям. # Инструкции - Всегда приветствуй пользователя фразой: "Здравствуйте, вы обратились в NewTelco, чем могу помочь?" - Всегда обращайся к пользователю на "Вы" - Всегда вызывай инструмент перед ответом на фактологические вопросы о компании, её услугах, продуктах или аккаунте пользователя. Используй только полученный контекст и никогда не полагайся на свои знания. - Но если у тебя недостаточно информации для вызова инструмента, запроси у пользователя необходимые данные. - Переводи на живого оператора по запросу пользователя. - Не обсуждай запрещённые темы (политику, религию, спорные текущие события, медицинские, юридические или финансовые консультации, личные беседы, внутренние операции компании или критику людей/компаний). - Используй примеры фраз, где уместно, но никогда не повторяй одну и ту же фразу в одном диалоге. Меняй формулировки, чтобы избежать повторов и адаптировать ответ под пользователя. - Всегда следуй предоставленному формату вывода сообщений, включая цитирование источников для любых фактологических утверждений из документов. - Перед и после вызова инструмента всегда уведомляй пользователя соответствующим сообщением. - Сохраняй профессиональный и лаконичный тон во всех ответах, используй эмодзи между предложениями. - Если запрос решён, спроси, можешь ли ты чем-то ещё помочь. # Точные шаги (для каждого ответа) 1. При необходимости вызови инструменты для выполнения запроса. Всегда уведомляй пользователя до и после вызова инструмента. 2. В ответе пользователю: a. Продемонстрируй активное слушание, повторив его запрос. b. Ответь в соответствии с инструкциями выше. # Примеры фраз ## При отклонении в сторону запрещённой темы - "Извините, но я не могу обсуждать эту тему. Могу ли помочь с чем-то ещё?" - "Я не могу предоставить информацию по этому вопросу, но с радостью помогу с другими." ## Перед вызовом инструмента - "Чтобы помочь вам, мне нужно уточнить некоторые данные." - "Позвольте мне проверить эту информацию — один момент." - "Сейчас я запрошу актуальные данные для вас." ## После вызова инструмента - "Вот что я нашёл: [ответ]" - "Итак, вот результаты: [ответ]" # Формат вывода - Всегда отправляй итоговый ответ пользователю. - При предоставлении фактов из контекста всегда указывай источники сразу после утверждения. Формат цитирования: - Для одного источника: [НАЗВАНИЕ](ID) - Для нескольких: [НАЗВАНИЕ](ID), [НАЗВАНИЕ](ID) - Отвечай только о компании, её политике, продуктах или аккаунте клиента, и только на основе предоставленного контекста. Не отвечай на вопросы вне этого круга. # Пример ## Пользователь Можете рассказать о ваших семейных тарифах? ## Ассистент ```json { "role": "assistant", "content": "Здравствуйте, вы обратились в NewTelco, чем могу помочь? 😊🎉\n\nВы хотели узнать о наших семейных тарифах. 🤝 Позвольте мне проверить эту информацию — один момент. 🚀", "tool_calls": [ { "id": "call-1", "type": "function", "function": { "name": "lookup_policy_document", "arguments": "{\"topic\": \"семейные тарифы\"}" } } ] } // После вызова инструмента ассистент продолжает: json { "role": "assistant", "content": "Вот что я нашёл: 🎉 Наш семейный тариф включает до пяти номеров с общим трафиком и скидкой 10% за каждый дополнительный номер [Политика семейных тарифов](ID-010). 📱 Могу ли помочь чем-то ещё? 😊" } """ get_policy_doc = { "type": "function", "name": "lookup_policy_document", "description": "Инструмент для поиска внутренних документов и политики по теме или ключевому слову.", "parameters": { "strict": True, "type": "object",В "properties": { "topic": { "type": "string", "description": "Тема или ключевое слово для поиска в политике или документах компании.", }, }, "required": ["topic"], "additionalProperties": False, }, } get_user_acct = { "type": "function", "name": "get_user_account_info", "description": "Инструмент для получения информации об аккаунте пользователя", "parameters": { "strict": True, "type": "object", "properties": { "phone_number": { "type": "string", "description": "Формат: '(xxx) xxx-xxxx'", }, }, "required": ["phone_number"], "additionalProperties": False, }, } response = client.responses.create( instructions=SYS_PROMPT_CUSTOMER_SERVICE, model="gpt-4.1-2025-04-14", tools=[get_policy_doc, get_user_acct], input="Почему мой последний счёт был таким большим?" # input="Сколько будет стоить международная связь? Я еду во Францию.", ) response.to_dict()["output"]

5. Общие советы

Структура промпта

Чтобы составить идеальный промпт, используйте следующую структуру:

# Роль нейросети и цель задачи # Инструкции ## Подкатегории для детализированных инструкций # Шаги логического рассуждения # Формат вывода # Примеры ## Пример 1 ## Пример 2 ## … # Контекст # Финальные инструкции и указание мыслить пошагово

Если надо, добавляйте или убирайте разделы. Экспериментируйте с формулировками, чтобы найти оптимальное решение вашей задачи.

Разделители контекста (Delimiters)

Их используют, чтобы запрос интерпретировался однозначно. Эти «визуальные маркеры» помогают модели чётко понимать, где начинается и где заканчивается разная информация в вашем запросе. Они бывают текстовые (---, ###, """, ***), кодовые ( , , ` ) и JSON/XML ({}, <>, []).

Вот как мы рекомендуем их применять.

1. Используйте разделители, если ваш промпт длиннее 2–3 предложений или содержит смешанные типы данных (код, текст, примеры).

2. При оформлении запроса, используйте элементы Markdown – тестовые разделители. Например:

### Инструкция Напиши код на Python. ### Входные данные ```python numbers = [1, 2, 3] ``` ### Ограничения - Не используй циклы. ---

3. Используйте XML-теги, если вам нужно разбить информацию на чёткие блоки и указать тип содержимого (задача, пример, код, контекст). Как уточняют разработчики, новая модель лучше понимает XML-теги, чем предыдущие. Пример использования:

<prompt> <role>Ты — помощник-программист.</role> <command>Напиши функцию на Python, которая считает факториал.</command> <format>Верни ответ в JSON с ключом "code".</format> </prompt>

4. Если ваша задача объёмная и сложная, используйте JSON (JavaScript Object Notation). Его разделители модели понимают лучше всего:

  • Фигурные скобки { } обозначают объект (набор пар «ключ: значение»).
  • Квадратные скобки [ ] обозначают массив (список значений).
  • Двоеточие : разделяет ключ и значение в объекте и т. д.

Упрощенный пример, как можно использовать JSON:

{ "request": { "task": "Переведи текст на французский", "input": { "text": "Привет, как дела?", "source_language": "ru", "target_language": "fr" }, "format": { "type": "json", "fields": ["translation", "confidence_score"] } } }

GPT-4.1 понимает структуру в разных форматах. Опирайтесь на свой опыт и цели при выборе подходящих разделителей.

Итог. GPT-4.1 точнее прошлых моделей Open AI, но есть нюансы

Новая версия значительно превосходит предыдущие в точности выполнения задач и управляемости. Строго следует инструкциям, реже даёт неожиданные ответы и «галлюцинирует». Эффективно работает с длинным контекстом, до 1 млн токенов.

Однако GPT-4.1 требует более чётких указаний. Неявные формулировки могут привести к ошибкам. Учитывайте это, экспериментируя с промптами, и ориентируйтесь на наши примеры.

Приложение для разработчиков: Как создавать и применять diff (различия между версиями кода)

Diff — это инструмент, который показывает изменения в коде (например, что удалено или добавлено между версиями). Пользователи отмечали, что точность этого инструмента критически важна для работы с кодом.

В GPT-4.1 улучшилась поддержка любых форматов diff, она может генерировать его, например, в Unified Diff или Git Diff, если даны чёткие инструкции.

Рекомендованный формат

Приводим конкретный формат diff, на котором модель тщательно обучалась. Это упростит работу, особенно новичкам:

– не нужно гадать, как правильно оформить diff;

– модель работает с этим форматом максимально точно.

Покажем, как применять патч с diff рекомендованного формата.

Применяем патч

Эту пользовательскую утилиту необходимо включить в работу с кодом.

APPLY_PATCH_TOOL_DESC = """Это пользовательская утилита, которая упрощает добавление, удаление, перемещение или редактирование файлов с кодом. `apply_patch` позволяет применять изменения (diff/patch) к файлу, но использует уникальный формат описания изменений, поэтому внимательно изучи эти инструкции. Для использования команды `apply_patch` передай сообщение следующей структуры в параметр "input": %%bash apply_patch <<"EOF" *** Begin Patch [YOUR_PATCH] *** End Patch EOF Где [YOUR_PATCH] — это само содержание изменений в следующем формате V4A diff: *** [ACTION] File: [path/to/file] -> ACTION может быть одним из: Add, Update или Delete. Для каждого изменяемого фрагмента кода повтори следующее: [context_before] -> См. ниже инструкции по контексту. - [old_code] -> Старый код, перед строкой ставится минус. + [new_code] -> Новый код, перед строкой ставится плюс. [context_after] -> См. ниже инструкции по контексту. Инструкции для [context_before] и [context_after]: - По умолчанию показывай 3 строки кода до и 3 строки после каждого изменения. Если изменения находятся в пределах 3 строк друг от друга, НЕ дублируй строки [context_after] первого изменения в [context_before] второго. - Если 3 строк контекста недостаточно для однозначной идентификации фрагмента кода в файле, используй оператор @@ для указания класса или функции, к которой относится фрагмент. Например: @@ class BaseClass [3 строки контекста до] - [old_code] + [new_code] [3 строки контекста после] - Если блок кода повторяется в классе или функции настолько часто, что даже один оператор @@ и 3 строки контекста не позволяют однозначно идентифицировать фрагмент, используй несколько операторов @@ для перехода к нужному контексту. Например: @@ class BaseClass @@ def method(): [3 строки контекста до] - [old_code] + [new_code] [3 строки контекста после] Обрати внимание, что в этом формате diff мы не используем номера строк, так как контекста достаточно для однозначной идентификации кода. Ниже приведен пример сообщения, которое можно передать в параметр "input" для применения изменений: %%bash apply_patch <<"EOF" *** Begin Patch *** Update File: pygorithm/searching/binary_search.py @@ class BaseClass @@ def search(): - pass + raise NotImplementedError() @@ class Subclass @@ def search(): - pass + raise NotImplementedError() *** End Patch EOF """ APPLY_PATCH_TOOL = { "name": "apply_patch", "description": APPLY_PATCH_TOOL_DESC, "parameters": { "type": "object", "properties": { "input": { "type": "string", "description": "Команда apply_patch, которую требуется выполнить.", } }, "required": ["input"], }, }

Эталонная реализация: apply_patch.py

Этот код использовался для тренировки модели. Даём его в оригинале. Чтобы GPT-4.1 мог выполнять команды, сделайте код доступным в соответствующей оболочке как apply_patch.

#!/usr/bin/env python3 """ A self-contained **pure-Python 3.9+** utility for applying human-readable “pseudo-diff” patch files to a collection of text files. """ from __future__ import annotations import pathlib from dataclasses import dataclass, field from enum import Enum from typing import ( Callable, Dict, List, Optional, Tuple, Union, ) # --------------------------------------------------------------------------- # # Domain objects # --------------------------------------------------------------------------- # class ActionType(str, Enum): ADD = "add" DELETE = "delete" UPDATE = "update" @dataclass class FileChange: type: ActionType old_content: Optional[str] = None new_content: Optional[str] = None move_path: Optional[str] = None @dataclass class Commit: changes: Dict[str, FileChange] = field(default_factory=dict) # --------------------------------------------------------------------------- # # Exceptions # --------------------------------------------------------------------------- # class DiffError(ValueError): """Any problem detected while parsing or applying a patch.""" # --------------------------------------------------------------------------- # # Helper dataclasses used while parsing patches # --------------------------------------------------------------------------- # @dataclass class Chunk: orig_index: int = -1 del_lines: List[str] = field(default_factory=list) ins_lines: List[str] = field(default_factory=list) @dataclass class PatchAction: type: ActionType new_file: Optional[str] = None chunks: List[Chunk] = field(default_factory=list) move_path: Optional[str] = None @dataclass class Patch: actions: Dict[str, PatchAction] = field(default_factory=dict) # --------------------------------------------------------------------------- # # Patch text parser # --------------------------------------------------------------------------- # @dataclass class Parser: current_files: Dict[str, str] lines: List[str] index: int = 0 patch: Patch = field(default_factory=Patch) fuzz: int = 0 # ------------- low-level helpers -------------------------------------- # def _cur_line(self) -> str: if self.index >= len(self.lines): raise DiffError("Unexpected end of input while parsing patch") return self.lines[self.index] @staticmethod def _norm(line: str) -> str: """Strip CR so comparisons work for both LF and CRLF input.""" return line.rstrip("\r") # ------------- scanning convenience ----------------------------------- # def is_done(self, prefixes: Optional[Tuple[str, ...]] = None) -> bool: if self.index >= len(self.lines): return True if ( prefixes and len(prefixes) > 0 and self._norm(self._cur_line()).startswith(prefixes) ): return True return False def startswith(self, prefix: Union[str, Tuple[str, ...]]) -> bool: return self._norm(self._cur_line()).startswith(prefix) def read_str(self, prefix: str) -> str: """ Consume the current line if it starts with *prefix* and return the text **after** the prefix. Raises if prefix is empty. """ if prefix == "": raise ValueError("read_str() requires a non-empty prefix") if self._norm(self._cur_line()).startswith(prefix): text = self._cur_line()[len(prefix) :] self.index += 1 return text return "" def read_line(self) -> str: """Return the current raw line and advance.""" line = self._cur_line() self.index += 1 return line # ------------- public entry point -------------------------------------- # def parse(self) -> None: while not self.is_done(("*** End Patch",)): # ---------- UPDATE ---------- # path = self.read_str("*** Update File: ") if path: if path in self.patch.actions: raise DiffError(f"Duplicate update for file: {path}") move_to = self.read_str("*** Move to: ") if path not in self.current_files: raise DiffError(f"Update File Error - missing file: {path}") text = self.current_files[path] action = self._parse_update_file(text) action.move_path = move_to or None self.patch.actions[path] = action continue # ---------- DELETE ---------- # path = self.read_str("*** Delete File: ") if path: if path in self.patch.actions: raise DiffError(f"Duplicate delete for file: {path}") if path not in self.current_files: raise DiffError(f"Delete File Error - missing file: {path}") self.patch.actions[path] = PatchAction(type=ActionType.DELETE) continue # ---------- ADD ---------- # path = self.read_str("*** Add File: ") if path: if path in self.patch.actions: raise DiffError(f"Duplicate add for file: {path}") if path in self.current_files: raise DiffError(f"Add File Error - file already exists: {path}") self.patch.actions[path] = self._parse_add_file() continue raise DiffError(f"Unknown line while parsing: {self._cur_line()}") if not self.startswith("*** End Patch"): raise DiffError("Missing *** End Patch sentinel") self.index += 1 # consume sentinel # ------------- section parsers ---------------------------------------- # def _parse_update_file(self, text: str) -> PatchAction: action = PatchAction(type=ActionType.UPDATE) lines = text.split("\n") index = 0 while not self.is_done( ( "*** End Patch", "*** Update File:", "*** Delete File:", "*** Add File:", "*** End of File", ) ): def_str = self.read_str("@@ ") section_str = "" if not def_str and self._norm(self._cur_line()) == "@@": section_str = self.read_line() if not (def_str or section_str or index == 0): raise DiffError(f"Invalid line in update section:\n{self._cur_line()}") if def_str.strip(): found = False if def_str not in lines[:index]: for i, s in enumerate(lines[index:], index): if s == def_str: index = i + 1 found = True break if not found and def_str.strip() not in [ s.strip() for s in lines[:index] ]: for i, s in enumerate(lines[index:], index): if s.strip() == def_str.strip(): index = i + 1 self.fuzz += 1 found = True break next_ctx, chunks, end_idx, eof = peek_next_section(self.lines, self.index) new_index, fuzz = find_context(lines, next_ctx, index, eof) if new_index == -1: ctx_txt = "\n".join(next_ctx) raise DiffError( f"Invalid {'EOF ' if eof else ''}context at {index}:\n{ctx_txt}" ) self.fuzz += fuzz for ch in chunks: ch.orig_index += new_index action.chunks.append(ch) index = new_index + len(next_ctx) self.index = end_idx return action def _parse_add_file(self) -> PatchAction: lines: List[str] = [] while not self.is_done( ("*** End Patch", "*** Update File:", "*** Delete File:", "*** Add File:") ): s = self.read_line() if not s.startswith("+"): raise DiffError(f"Invalid Add File line (missing '+'): {s}") lines.append(s[1:]) # strip leading '+' return PatchAction(type=ActionType.ADD, new_file="\n".join(lines)) # --------------------------------------------------------------------------- # # Helper functions # --------------------------------------------------------------------------- # def find_context_core( lines: List[str], context: List[str], start: int ) -> Tuple[int, int]: if not context: return start, 0 for i in range(start, len(lines)): if lines[i : i + len(context)] == context: return i, 0 for i in range(start, len(lines)): if [s.rstrip() for s in lines[i : i + len(context)]] == [ s.rstrip() for s in context ]: return i, 1 for i in range(start, len(lines)): if [s.strip() for s in lines[i : i + len(context)]] == [ s.strip() for s in context ]: return i, 100 return -1, 0 def find_context( lines: List[str], context: List[str], start: int, eof: bool ) -> Tuple[int, int]: if eof: new_index, fuzz = find_context_core(lines, context, len(lines) - len(context)) if new_index != -1: return new_index, fuzz new_index, fuzz = find_context_core(lines, context, start) return new_index, fuzz + 10_000 return find_context_core(lines, context, start) def peek_next_section( lines: List[str], index: int ) -> Tuple[List[str], List[Chunk], int, bool]: old: List[str] = [] del_lines: List[str] = [] ins_lines: List[str] = [] chunks: List[Chunk] = [] mode = "keep" orig_index = index while index < len(lines): s = lines[index] if s.startswith( ( "@@", "*** End Patch", "*** Update File:", "*** Delete File:", "*** Add File:", "*** End of File", ) ): break if s == "***": break if s.startswith("***"): raise DiffError(f"Invalid Line: {s}") index += 1 last_mode = mode if s == "": s = " " if s[0] == "+": mode = "add" elif s[0] == "-": mode = "delete" elif s[0] == " ": mode = "keep" else: raise DiffError(f"Invalid Line: {s}") s = s[1:] if mode == "keep" and last_mode != mode: if ins_lines or del_lines: chunks.append( Chunk( orig_index=len(old) - len(del_lines), del_lines=del_lines, ins_lines=ins_lines, ) ) del_lines, ins_lines = [], [] if mode == "delete": del_lines.append(s) old.append(s) elif mode == "add": ins_lines.append(s) elif mode == "keep": old.append(s) if ins_lines or del_lines: chunks.append( Chunk( orig_index=len(old) - len(del_lines), del_lines=del_lines, ins_lines=ins_lines, ) ) if index < len(lines) and lines[index] == "*** End of File": index += 1 return old, chunks, index, True if index == orig_index: raise DiffError("Nothing in this section") return old, chunks, index, False # --------------------------------------------------------------------------- # # Patch → Commit and Commit application # --------------------------------------------------------------------------- # def _get_updated_file(text: str, action: PatchAction, path: str) -> str: if action.type is not ActionType.UPDATE: raise DiffError("_get_updated_file called with non-update action") orig_lines = text.split("\n") dest_lines: List[str] = [] orig_index = 0 for chunk in action.chunks: if chunk.orig_index > len(orig_lines): raise DiffError( f"{path}: chunk.orig_index {chunk.orig_index} exceeds file length" ) if orig_index > chunk.orig_index: raise DiffError( f"{path}: overlapping chunks at {orig_index} > {chunk.orig_index}" ) dest_lines.extend(orig_lines[orig_index : chunk.orig_index]) orig_index = chunk.orig_index dest_lines.extend(chunk.ins_lines) orig_index += len(chunk.del_lines) dest_lines.extend(orig_lines[orig_index:]) return "\n".join(dest_lines) def patch_to_commit(patch: Patch, orig: Dict[str, str]) -> Commit: commit = Commit() for path, action in patch.actions.items(): if action.type is ActionType.DELETE: commit.changes[path] = FileChange( type=ActionType.DELETE, old_content=orig[path] ) elif action.type is ActionType.ADD: if action.new_file is None: raise DiffError("ADD action without file content") commit.changes[path] = FileChange( type=ActionType.ADD, new_content=action.new_file ) elif action.type is ActionType.UPDATE: new_content = _get_updated_file(orig[path], action, path) commit.changes[path] = FileChange( type=ActionType.UPDATE, old_content=orig[path], new_content=new_content, move_path=action.move_path, ) return commit # --------------------------------------------------------------------------- # # User-facing helpers # --------------------------------------------------------------------------- # def text_to_patch(text: str, orig: Dict[str, str]) -> Tuple[Patch, int]: lines = text.splitlines() # preserves blank lines, no strip() if ( len(lines) < 2 or not Parser._norm(lines[0]).startswith("*** Begin Patch") or Parser._norm(lines[-1]) != "*** End Patch" ): raise DiffError("Invalid patch text - missing sentinels") parser = Parser(current_files=orig, lines=lines, index=1) parser.parse() return parser.patch, parser.fuzz def identify_files_needed(text: str) -> List[str]: lines = text.splitlines() return [ line[len("*** Update File: ") :] for line in lines if line.startswith("*** Update File: ") ] + [ line[len("*** Delete File: ") :] for line in lines if line.startswith("*** Delete File: ") ] def identify_files_added(text: str) -> List[str]: lines = text.splitlines() return [ line[len("*** Add File: ") :] for line in lines if line.startswith("*** Add File: ") ] # --------------------------------------------------------------------------- # # File-system helpers # --------------------------------------------------------------------------- # def load_files(paths: List[str], open_fn: Callable[[str], str]) -> Dict[str, str]: return {path: open_fn(path) for path in paths} def apply_commit( commit: Commit, write_fn: Callable[[str, str], None], remove_fn: Callable[[str], None], ) -> None: for path, change in commit.changes.items(): if change.type is ActionType.DELETE: remove_fn(path) elif change.type is ActionType.ADD: if change.new_content is None: raise DiffError(f"ADD change for {path} has no content") write_fn(path, change.new_content) elif change.type is ActionType.UPDATE: if change.new_content is None: raise DiffError(f"UPDATE change for {path} has no new content") target = change.move_path or path write_fn(target, change.new_content) if change.move_path: remove_fn(path) def process_patch( text: str, open_fn: Callable[[str], str], write_fn: Callable[[str, str], None], remove_fn: Callable[[str], None], ) -> str: if not text.startswith("*** Begin Patch"): raise DiffError("Patch text must start with *** Begin Patch") paths = identify_files_needed(text) orig = load_files(paths, open_fn) patch, _fuzz = text_to_patch(text, orig) commit = patch_to_commit(patch, orig) apply_commit(commit, write_fn, remove_fn) return "Done!" # --------------------------------------------------------------------------- # # Default FS helpers # --------------------------------------------------------------------------- # def open_file(path: str) -> str: with open(path, "rt", encoding="utf-8") as fh: return fh.read() def write_file(path: str, content: str) -> None: target = pathlib.Path(path) target.parent.mkdir(parents=True, exist_ok=True) with target.open("wt", encoding="utf-8") as fh: fh.write(content) def remove_file(path: str) -> None: pathlib.Path(path).unlink(missing_ok=True) # --------------------------------------------------------------------------- # # CLI entry-point # --------------------------------------------------------------------------- # def main() -> None: import sys patch_text = sys.stdin.read() if not patch_text: print("Please pass patch text through stdin", file=sys.stderr) return try: result = process_patch(patch_text, open_file, write_file, remove_file) except DiffError as exc: print(exc, file=sys.stderr) return print(result) if __name__ == "__main__": main()

Другие эффективные форматы diff

Если хотите попробовать альтернативные форматы описания изменений в коде, то в ходе тестирования высокую точность показали SEARCH/REPLACE diff и псевдо-XML.

Общие ключевые особенности этих форматов:

  1. Не используют номера строк — вместо этого опираются на контекст.
  2. Чётко разделяют старый и новый код — явно указывают, что на что заменить.

Пример использования формата SEARCH/REPLACE

SEARCH_REPLACE_DIFF_EXAMPLE = """ path/to/file.py ``` >>>>>>> SEARCH def search(): pass ======= def search(): raise NotImplementedError() <<<<<<< REPLACE """

Пример использования формата псевдо-XML

PSEUDO_XML_DIFF_EXAMPLE = """ <edit> <file> path/to/file.py </file> <old_code> def search(): pass </old_code> <new_code> def search(): raise NotImplementedError() </new_code> </edit> """

Спасибо за чтение. Если друг захотите потестить GPT-4,1 в нашем сервисе с нейросетями - велкам.

9
4
1
5 комментариев