Семейство моделей GPT-4.1 представляет собой значительный шаг вперед по сравнению с GPT-4o в возможностях кодирования, следования инструкциям и длинного контекста. В этом руководстве по подсказкам мы собираем ряд важных советов по подсказкам, полученных в результате обширного внутреннего тестирования, чтобы помочь разработчикам в полной мере использовать улучшенные возможности этого нового семейства моделей.
Многие типичные передовые практики по-прежнему применимы к GPT-4.1, такие как предоставление примеров контекста, максимально конкретное и ясное изложение инструкций и побуждение к планированию с помощью подсказок для максимизации интеллекта модели. Однако мы ожидаем, что для получения максимальной отдачи от этой модели потребуется некоторая быстрая миграция. GPT-4.1 обучен следовать инструкциям более точно и буквально, чем его предшественники, которые, как правило, более либерально выводят намерения из подсказок пользователя и системы. Однако это также означает, что GPT-4.1 обладает высокой управляемостью и реагирует на четко определенные подсказки — если поведение модели отличается от ожидаемого, одного предложения, твердо и недвусмысленно разъясняющего желаемое поведение, почти всегда достаточно, чтобы направить модель по курсу.
Пожалуйста, продолжайте читать, чтобы ознакомиться с примерами, которые вы можете использовать в качестве справочного материала, и помните, что, хотя это руководство широко применимо, ни один совет не является универсальным. Инженерия ИИ по своей сути является эмпирической дисциплиной, а большие языковые модели по своей сути недетерминированы; в дополнение к следованию этому руководству мы советуем создавать информативные оценки и часто выполнять итерации, чтобы убедиться, что ваши изменения в оперативной инженерии приносят пользу для вашего варианта использования.
1. Агентские рабочие процессы
GPT-4.1 — отличное место для создания агентских рабочих процессов. В обучении моделей мы подчеркивали предоставление разнообразного спектра траекторий решения агентских проблем, и наша агентская обвязка для модели достигает передовой производительности для нерассуждающих моделей на SWE-bench Verified, решая 55% проблем.
Системные напоминания
Чтобы полностью использовать агентские возможности GPT-4.1, мы рекомендуем включать три ключевых типа напоминаний во все агентские подсказки. Следующие подсказки оптимизированы специально для рабочего процесса кодирования агента, но могут быть легко изменены для общих случаев использования агента.
- Устойчивость: это гарантирует, что модель понимает, что она входит в очередь с несколькими сообщениями, и не позволяет ей преждевременно вернуть управление пользователю. Наш пример следующий:
You are an agent - please keep going until the user’s query is completely resolved, before ending your turn and yielding back to the user. Only terminate your turn when you are sure that the problem is solved.
- Вызов инструмента: это побуждает модель в полной мере использовать свои инструменты и снижает вероятность галлюцинаций или угадывания ответа. Наш пример следующий:
If you are not sure about file content or codebase structure pertaining to the user’s request, use your tools to read files and gather the relevant information: do NOT guess or make up an answer.
- Планирование [необязательно]: при желании это гарантирует, что модель явно планирует и отражает каждый вызов инструмента в тексте, вместо того, чтобы выполнять задачу, объединяя в цепочку ряд только вызовов инструментов. Наш пример следующий:
You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully.
GPT-4.1 обучен очень точно реагировать как на пользовательские инструкции, так и на системные подсказки в агентской среде. Модель строго придерживалась этих трех простых инструкций и увеличила наш внутренний SWE-bench Verified балл почти на 20% - поэтому мы настоятельно рекомендуем начинать любую подсказку агента с четких напоминаний, охватывающих три категории, перечисленные выше. В целом мы обнаружили, что эти три инструкции преобразуют модель из состояния, похожего на чат-бот, в гораздо более «усердного» агента, продвигая взаимодействие вперед автономно и независимо.
Инструментальные вызовы
По сравнению с предыдущими моделями GPT-4.1 прошла больше обучения по эффективному использованию инструментов, переданных в качестве аргументов в запросе API OpenAI. Мы призываем разработчиков использовать исключительно поле tools для передачи инструментов, а не вручную вводить описания инструментов в приглашение и писать отдельный парсер для вызовов инструментов, как некоторые сообщали в прошлом. Это лучший способ минимизировать ошибки и гарантировать, что модель останется в распределении во время траекторий вызова инструментов — в наших собственных экспериментах мы наблюдали 2%-ное увеличение скорости прохождения SWE-bench Verified при использовании описаний инструментов, проанализированных API, по сравнению с ручным введением схем в системное приглашение.
Разработчики должны четко называть инструменты, чтобы указать их назначение, и добавлять четкое, подробное описание в поле «description» инструмента. Аналогично, для каждого параметра инструмента опирайтесь на хорошее наименование и описание, чтобы обеспечить надлежащее использование. Если ваш инструмент особенно сложен и вы хотите предоставить примеры использования инструмента, мы рекомендуем вам создать # Examplesраздел в системном приглашении и поместить примеры туда, а не добавлять их в поле «description», которое должно оставаться подробным, но относительно кратким. Предоставление примеров может быть полезным для указания того, когда использовать инструменты, следует ли включать пользовательский текст вместе с вызовами инструментов и какие параметры подходят для различных входных данных. Помните, что вы можете использовать «Generate Anything» в Prompt Playground, чтобы получить хорошую отправную точку для ваших новых определений инструментов.
Планирование, вызванное подсказками, и цепочка мыслей
Как уже упоминалось, разработчики могут опционально подсказывать агентам, созданным с помощью GPT-4.1, планировать и размышлять между вызовами инструментов, вместо того, чтобы молча вызывать инструменты в непрерывной последовательности. GPT-4.1 не является моделью рассуждений — это означает, что она не создает внутреннюю цепочку мыслей перед ответом — но в подсказке разработчик может побудить модель создать явный пошаговый план, используя любой вариант компонента подсказки «Планирование», показанного выше. Это можно рассматривать как модель «размышлений вслух». В нашем эксперименте с агентской задачей SWE-bench Verified, побуждение явного планирования увеличило процент успешных попыток на 4%.
Пример запроса: проверено SWE-bench
Ниже мы делимся агентской подсказкой, которую мы использовали для достижения наивысшего балла на 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 = """ You will be tasked to fix an issue from an open-source repository. Your thinking should be thorough and so it's fine if it's very long. You can think step by step before and after each action you decide to take. You MUST iterate and keep going until the problem is solved. You already have everything you need to solve this problem in the /testbed folder, even without internet connection. I want you to fully solve this autonomously before coming back to me. Only terminate your turn when you are sure that the problem is solved. Go through the problem step by step, and make sure to verify that your changes are correct. NEVER end your turn without having solved the problem, and when you say you are going to make a tool call, make sure you ACTUALLY make the tool call, instead of ending your turn. THE PROBLEM CAN DEFINITELY BE SOLVED WITHOUT THE INTERNET. Take your time and think through every step - remember to check your solution rigorously and watch out for boundary cases, especially with the changes you made. Your solution must be perfect. If not, continue working on it. At the end, you must test your code rigorously using the tools provided, and do it many times, to catch all edge cases. If it is not robust, iterate more and make it perfect. Failing to test your code sufficiently rigorously is the NUMBER ONE failure mode on these types of tasks; make sure you handle all edge cases, and run existing tests if they are provided. You MUST plan extensively before each function call, and reflect extensively on the outcomes of the previous function calls. DO NOT do this entire process by making function calls only, as this can impair your ability to solve the problem and think insightfully. # Workflow ## High-Level Problem Solving Strategy 1. Understand the problem deeply. Carefully read the issue and think critically about what is required. 2. Investigate the codebase. Explore relevant files, search for key functions, and gather context. 3. Develop a clear, step-by-step plan. Break down the fix into manageable, incremental steps. 4. Implement the fix incrementally. Make small, testable code changes. 5. Debug as needed. Use debugging techniques to isolate and resolve issues. 6. Test frequently. Run tests after each change to verify correctness. 7. Iterate until the root cause is fixed and all tests pass. 8. Reflect and validate comprehensively. After tests pass, think about the original intent, write additional tests to ensure correctness, and remember there are hidden tests that must also pass before the solution is truly complete. Refer to the detailed sections below for more information on each step. ## 1. Deeply Understand the Problem Carefully read the issue and think hard about a plan to solve it before coding. ## 2. Codebase Investigation - Explore relevant files and directories. - Search for key functions, classes, or variables related to the issue. - Read and understand relevant code snippets. - Identify the root cause of the problem. - Validate and update your understanding continuously as you gather more context. ## 3. Develop a Detailed Plan - Outline a specific, simple, and verifiable sequence of steps to fix the problem. - Break down the fix into small, incremental changes. ## 4. Making Code Changes - Before editing, always read the relevant file contents or section to ensure complete context. - If a patch is not applied correctly, attempt to reapply it. - Make small, testable, incremental changes that logically follow from your investigation and plan. ## 5. Debugging - Make code changes only if you have high confidence they can solve the problem - When debugging, try to determine the root cause rather than addressing symptoms - Debug for as long as needed to identify the root cause and identify a fix - Use print statements, logs, or temporary code to inspect program state, including descriptive statements or error messages to understand what's happening - To test hypotheses, you can also add test statements or functions - Revisit your assumptions if unexpected behavior occurs. ## 6. Testing - Run tests frequently using `!python3 run_tests.py` (or equivalent). - After each change, verify correctness by running relevant tests. - If tests fail, analyze failures and revise your patch. - Write additional tests if needed to capture important behaviors or edge cases. - Ensure all tests pass before finalizing. ## 7. Final Verification - Confirm the root cause is fixed. - Review your solution for logic correctness and robustness. - Iterate until you are extremely confident the fix is complete and all tests pass. ## 8. Final Reflection and Additional Testing - Reflect carefully on the original intent of the user and the problem statement. - Think about potential edge cases or scenarios that may not be covered by existing tests. - Write additional tests that would need to pass to fully validate the correctness of your solution. - Run these new tests and ensure they all pass. - Be aware that there are additional hidden tests that must also pass for the solution to be successful. - Do not assume the task is complete just because the visible tests pass; continue refining until you are confident the fix is robust and comprehensive. """ 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: Bug: Typerror..." ) response.to_dict()["output"]
[{'id': 'msg_67fe92df26ac819182ffafce9ff4e4fc07c7e06242e51f8b', 'content': [{'annotations': [], 'text': "Спасибо за отчет, но «Typerror» слишком расплывчато для меня, чтобы начать отладку прямо сейчас. **Чтобы добиться прогресса, мне нужно:** 1. Найти точный текст сообщения об ошибке (например, `'TypeError: ...'`). 2. Найти, в каком файле и в какой строке/функции/классе произошла ошибка. 3. Выяснить, что вызвало ошибку (тестовый файл, использование, шаги воспроизведения). 4. Найти первопричину и подробности. **Следующие шаги:** - Исследуйте файлы выходных данных ошибок/журналов/тестов на предмет сообщения Python `TypeError`. - Изучите соответствующие разделы кода на предмет проблемного использования типов. - Если возможно, воспроизведите ошибка локально. **План:** - Сначала я поищу тестовые файлы и вывод журнала в каталоге `/testbed`, который может содержать полное сообщение об ошибке и трассировку стека. Давайте начнем с перечисления содержимого каталога `/testbed`, чтобы найти подсказки.", 'type': 'output_text'}], 'role': 'assistant', 'status': 'completed', 'type': 'message'}, {'arguments': '{"input":"!ls -l /testbed"}', 'call_id': 'call_frnxyJgKi5TsBem0nR9Zuzdw', 'name': 'python', 'type': 'function_call', 'id': 'fc_67fe92e3da7081918fc18d5c96dddc1c07c7e06242e51f8b', 'статус': 'завершено'}]2. Длинный контекст
GPT-4.1 имеет производительное окно контекста ввода токенов на 1 М и полезен для различных длительных контекстных задач, включая структурированный анализ документов, повторное ранжирование, выбор релевантной информации с игнорированием нерелевантного контекста и выполнение многоадресных рассуждений с использованием контекста.
Оптимальный размер контекста
Мы наблюдаем очень хорошую производительность при оценке иголки в стоге сена вплоть до полного контекста токенов 1M, и мы наблюдаем очень высокую производительность при выполнении сложных задач с использованием как релевантного, так и нерелевантного кода и других документов. Однако производительность длинного контекста может ухудшиться, поскольку требуется извлечь больше элементов или выполнить сложные рассуждения, требующие знания состояния всего контекста (например, выполнение поиска по графу).
Настройка контекстной зависимости
Рассмотрите сочетание внешних и внутренних знаний мира, которые могут потребоваться для ответа на ваш вопрос. Иногда важно, чтобы модель использовала некоторые из своих собственных знаний для связи концепций или осуществления логических переходов, в то время как в других случаях желательно использовать только предоставленный контекст
# Instructions// for internal knowledge- Only use the documents in the provided External Context to answer the User Query. If you don't know the answer based on this context, you must respond "I don't have the information needed to answer that", even if a user insists on you answering the question.// For internal and external knowledge- By default, use the provided external context to answer the User Query, but if other basic knowledge is needed to answer, and you're confident in the answer, you can use some of your own knowledge to help answer the question.
Быстрая организация
Особенно при использовании длинного контекста размещение инструкций и контекста может повлиять на производительность. Если в вашей подсказке длинный контекст, в идеале поместите инструкции как в начале, так и в конце предоставленного контекста, поскольку мы обнаружили, что это работает лучше, чем только выше или ниже. Если вы предпочитаете, чтобы ваши инструкции были только один раз, то над предоставленным контекстом работает лучше, чем ниже.
3. Цепочка мыслей
Как упоминалось выше, GPT-4.1 не является моделью рассуждений, но побуждение модели думать шаг за шагом (называемое «цепочкой мыслей») может быть эффективным способом для модели разбить проблемы на более управляемые части, решить их и улучшить общее качество вывода, с компромиссом более высокой стоимости и задержки, связанной с использованием большего количества выходных токенов. Модель была обучена хорошо работать в агентных рассуждениях и решении реальных проблем, поэтому ей не должно потребоваться много подсказок для хорошей работы.
Мы рекомендуем начать с этой базовой цепочки мыслей в конце вашего задания:
...First, think carefully step by step about what documents are needed to answer the query. Then, print out the TITLE and ID of each document. Then, format the IDs into a list.
Отсюда вы должны улучшить подсказку цепочки мыслей (CoT), проведя аудит неудач в ваших конкретных примерах и оценках, и обратившись к систематическим ошибкам планирования и рассуждений с более подробными инструкциями. В подсказке CoT без ограничений могут быть различия в стратегиях, которые она пробует, и если вы увидите подход, который работает хорошо, вы можете кодифицировать эту стратегию в своей подсказке. Вообще говоря, ошибки, как правило, происходят из-за непонимания намерений пользователя, недостаточного сбора или анализа контекста или недостаточного или неправильного пошагового мышления, поэтому следите за ними и старайтесь решать их с помощью более самоуверенных инструкций.
Ниже приведен пример подсказки, предписывающей модели более методично сосредоточиться на анализе намерений пользователя и рассмотрении соответствующего контекста, прежде чем приступить к ответу.
# Reasoning Strategy1. Query Analysis: Break down and analyze the query until you're confident about what it might be asking. Consider the provided context to help clarify any ambiguous or confusing information.2. Context Analysis: Carefully select and analyze a large set of potentially relevant documents. Optimize for recall - it's okay if some are irrelevant, but the correct documents must be in this list, otherwise your final answer will be wrong. Analysis steps for each: a. Analysis: An analysis of how it may or may not be relevant to answering the query. b. Relevance rating: [high, medium, low, none]3. Synthesis: summarize which documents are most relevant and why, including all documents with a relevance rating of medium or higher.# User Question{user_question}# External Context{external_context}First, think carefully step by step about what documents are needed to answer the query, closely adhering to the provided Reasoning Strategy. Then, print out the TITLE and ID of each document. Then, format the IDs into a list.
4. Инструкция по выполнению
GPT-4.1 демонстрирует выдающуюся производительность следования инструкциям, которую разработчики могут использовать для точного формирования и управления выходными данными для своих конкретных вариантов использования. Разработчики часто широко запрашивают шаги агентного рассуждения, тон и голос ответа, информацию о вызове инструмента, форматирование выходных данных, темы, которых следует избегать, и многое другое. Однако, поскольку модель следует инструкциям более буквально, разработчикам может потребоваться включить явную спецификацию того, что делать или не делать. Кроме того, существующие подсказки, оптимизированные для других моделей, могут не сразу работать с этой моделью, поскольку существующие инструкции выполняются более тщательно, а неявные правила больше не выводятся так строго.
Рекомендуемый рабочий процесс
Вот наш рекомендуемый рабочий процесс для разработки и отладки инструкций в подсказках:
- Начните с общего раздела «Правила реагирования» или «Инструкции» с общими рекомендациями и маркированными списками.
- Если вы хотите изменить более конкретное поведение, добавьте раздел, чтобы указать дополнительные сведения для этой категории, например
# Sample Phrases. - Если вы хотите, чтобы модель выполнила определенные шаги в своем рабочем процессе, добавьте упорядоченный список и дайте модели указание выполнить эти шаги.
- Если поведение по-прежнему не работает так, как ожидалось:
- Проверьте наличие противоречивых, недостаточно определенных или неправильных инструкций и примеров. Если есть противоречивые инструкции, GPT-4.1 имеет тенденцию следовать той, которая ближе к концу подсказки.
- Добавьте примеры, демонстрирующие желаемое поведение; убедитесь, что все важные примеры поведения, продемонстрированные в ваших примерах, также указаны в ваших правилах.
- Обычно нет необходимости использовать заглавные буквы или другие поощрения, такие как взятки или чаевые. Мы рекомендуем начать без них и обращаться к ним только в случае необходимости для вашей конкретной подсказки. Обратите внимание, что если ваши существующие подсказки включают эти приемы, это может привести к тому, что GPT-4.1 будет уделять им слишком пристальное внимание.
Обратите внимание, что использование предпочитаемой вами среды разработки на базе искусственного интеллекта может оказаться очень полезным для итерации подсказок, включая проверку на согласованность или конфликты, добавление примеров или внесение последовательных обновлений, таких как добавление инструкции и обновление инструкций для ее демонстрации.
Распространенные виды отказов
Эти режимы сбоев не являются уникальными для GPT-4.1, но мы приводим их здесь для общей осведомленности и упрощения отладки.
- Инструктирование модели всегда следовать определенному поведению может иногда вызывать неблагоприятные эффекты. Например, если сказать «вы должны вызвать инструмент, прежде чем отвечать пользователю», модели могут галлюцинировать вводимые данные инструмента или вызывать инструмент с нулевыми значениями, если у них недостаточно информации. Добавление «если у вас недостаточно информации для вызова инструмента, попросите пользователя предоставить вам необходимую информацию» должно смягчить это.
- При предоставлении образцов фраз модели могут использовать эти цитаты дословно и начать звучать для пользователей повторяющимися. Убедитесь, что вы проинструктировали модель изменять их по мере необходимости.
- Без конкретных инструкций некоторые модели могут стремиться предоставить дополнительную прозу для объяснения своих решений или выводить больше форматирования в ответах, чем может быть желательно. Предоставьте инструкции и возможные примеры, чтобы помочь смягчить ситуацию.
Пример запроса: Служба поддержки клиентов
Это демонстрирует лучшие практики для вымышленного агента по обслуживанию клиентов. Обратите внимание на разнообразие правил, специфику, использование дополнительных разделов для большей детализации и пример, демонстрирующий точное поведение, которое включает все предыдущие правила.
Попробуйте запустить следующую ячейку блокнота — вы должны увидеть как сообщение пользователя, так и вызов инструмента, а сообщение пользователя должно начинаться с приветствия, затем возвращать его ответ, затем упоминать, что он собирается вызвать инструмент. Попробуйте изменить инструкции, чтобы сформировать поведение модели, или попробуйте другие сообщения пользователя, чтобы проверить производительность инструкции после.
SYS_PROMPT_CUSTOMER_SERVICE = """You are a helpful customer service agent working for NewTelco, helping a user efficiently fulfill their request while adhering closely to provided guidelines. # Instructions - Always greet the user with "Hi, you've reached NewTelco, how can I help you?" - Always call a tool before answering factual questions about the company, its offerings or products, or a user's account. Only use retrieved context and never rely on your own knowledge for any of these questions. - However, if you don't have enough information to properly call the tool, ask the user for the information you need. - Escalate to a human if the user requests. - Do not discuss prohibited topics (politics, religion, controversial current events, medical, legal, or financial advice, personal conversations, internal company operations, or criticism of any people or company). - Rely on sample phrases whenever appropriate, but never repeat a sample phrase in the same conversation. Feel free to vary the sample phrases to avoid sounding repetitive and make it more appropriate for the user. - Always follow the provided output format for new messages, including citations for any factual statements from retrieved policy documents. - If you're going to call a tool, always message the user with an appropriate message before and after calling the tool. - Maintain a professional and concise tone in all responses, and use emojis between sentences. - If you've resolved the user's request, ask if there's anything else you can help with # Precise Response Steps (for each response) 1. If necessary, call tools to fulfill the user's desired action. Always message the user before and after calling a tool to keep them in the loop. 2. In your response to the user a. Use active listening and echo back what you heard the user ask for. b. Respond appropriately given the above guidelines. # Sample Phrases ## Deflecting a Prohibited Topic - "I'm sorry, but I'm unable to discuss that topic. Is there something else I can help you with?" - "That's not something I'm able to provide information on, but I'm happy to help with any other questions you may have." ## Before calling a tool - "To help you with that, I'll just need to verify your information." - "Let me check that for you—one moment, please." - "I'll retrieve the latest details for you now." ## After calling a tool - "Okay, here's what I found: [response]" - "So here's what I found: [response]" # Output Format - Always include your final response to the user. - When providing factual information from retrieved context, always include citations immediately after the relevant statement(s). Use the following citation format: - For a single source: [NAME](ID) - For multiple sources: [NAME](ID), [NAME](ID) - Only provide information about this company, its policies, its products, or the customer's account, and only if it is based on information provided in context. Do not answer questions outside this scope. # Example ## User Can you tell me about your family plan options? ## Assistant Response 1 ### Message "Hi, you've reached NewTelco, how can I help you? ?? You'd like to know about our family plan options. ? Let me check that for you—one moment, please. ?" ### Tool Calls lookup_policy_document(topic="family plan options") // After tool call, the assistant would follow up with: ## Assistant Response 2 (after tool call) ### Message "Okay, here's what I found: ? Our family plan allows up to 5 lines with shared data and a 10% discount for each additional line [Family Plan Policy](ID-010). ? Is there anything else I can help you with today? ?" """ get_policy_doc = { "type": "function", "name": "lookup_policy_document", "description": "Tool to look up internal documents and policies by topic or keyword.", "parameters": { "strict": True, "type": "object", "properties": { "topic": { "type": "string", "description": "The topic or keyword to search for in company policies or documents.", }, }, "required": ["topic"], "additionalProperties": False, }, } get_user_acct = { "type": "function", "name": "get_user_account_info", "description": "Tool to get user account information", "parameters": { "strict": True, "type": "object", "properties": { "phone_number": { "type": "string", "description": "Formatted as '(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="How much will it cost for international service? I'm traveling to France.", # input="Why was my last bill so high?" ) response.to_dict()["output"]
[{'id': 'msg_67fe92d431548191b7ca6cd604b4784b06efc5beb16b3c5e', 'content': [{'annotations': [], 'text': "Привет, вы позвонили в NewTelco, чем я могу вам помочь? ?? Вы хотели бы узнать стоимость международной связи во время поездки во Францию. ?? Позвольте мне проверить для вас последние данные — одну минуту, пожалуйста. ?", 'type': 'output_text'}], 'role': 'assistant', 'status': 'completed', 'type': 'message'}, {'arguments': '{"topic":"стоимость международной связи во Франции"}', 'call_id': 'call_cF63DLeyhNhwfdyME3ZHd0yo', 'name': 'lookup_policy_document', 'type': 'function_call', 'id': 'fc_67fe92d5d6888191b6cd7cf57f707e4606efc5beb16b3c5e', 'status': 'completed'}]5. Общие советы
Структура подсказки
Для справки, вот хорошая отправная точка для структурирования ваших подсказок.
# Role and Objective# Instructions## Sub-categories for more detailed instructions# Reasoning Steps# Output Format# Examples## Example 1# Context# Final instructions and prompt to think step by step
Добавляйте или удаляйте разделы в соответствии с вашими потребностями и экспериментируйте, чтобы определить оптимальный вариант для вашего использования.
Разделители
Вот некоторые общие рекомендации по выбору лучших разделителей для вашего приглашения. Пожалуйста, обратитесь к разделу Длинный контекст для специальных соображений для этого типа контекста.
- Markdown: Мы рекомендуем начать отсюда и использовать заголовки markdown для основных разделов и подразделов (включая более глубокую иерархию, до H4+). Используйте встроенные обратные кавычки или блоки обратных кавычек для точного переноса кода, а также стандартные нумерованные или маркированные списки по мере необходимости.
- XML: Они также хорошо работают, и мы улучшили соответствие информации в XML с помощью этой модели. XML удобен для точного обертывания раздела, включая начало и конец, добавления метаданных к тегам для дополнительного контекста и включения вложенности. Вот пример использования тегов XML для вложения примеров в раздел примеров с входами и выходами для каждого:
<examples><example1 type="Abbreviate"><input>San Francisco</input><output>- SF</output></example1></examples>
- JSON высокоструктурирован и хорошо понимается моделью, особенно в контексте кодирования. Однако он может быть более многословным и требовать экранирования символов, что может добавить накладные расходы.
Руководство по добавлению большого количества документов или файлов во входной контекст:
- XML хорошо показал себя в ходе нашего длительного контекстного тестирования.
- Пример:
<doc id=1 title=”The Fox”>The quick brown fox jumps over the lazy dog</doc>
- Этот формат, предложенный Ли и др. ( ссылка ), также хорошо зарекомендовал себя в ходе нашего длительного контекстного тестирования.
- Пример:
ID: 1 | TITLE: The Fox | CONTENT: The quick brown fox jumps over the lazy dog
- JSON показал себя особенно плохо.
- Пример:
[{“id”: 1, “title”: “The Fox”, “content”: “The quick brown fox jumped over the lazy dog”}]
Модель обучена надежно понимать структуру в различных форматах. В общем, используйте свое суждение и подумайте о том, что предоставит ясную информацию и «выделится» для модели. Например, если вы извлекаете документы, содержащие много XML, разделитель на основе XML, вероятно, будет менее эффективным.
Предостережения
- В некоторых отдельных случаях мы наблюдали, как модель сопротивлялась созданию очень длинных, повторяющихся выходных данных, например, анализируя сотни элементов один за другим. Если это необходимо для вашего варианта использования, настоятельно поручите модели выводить эту информацию полностью и рассмотрите возможность разбить проблему на части или использовать более сжатый подход.
- Мы наблюдали некоторые редкие случаи, когда параллельные вызовы инструментов были некорректными. Мы рекомендуем протестировать это и рассмотреть возможность установки параметра parallel_tool_calls на false, если вы видите проблемы.
Приложение: Создание и применение различий файлов
Разработчики предоставили нам отзывы о том, что точная и правильно сформированная генерация различий является критически важной возможностью для выполнения задач, связанных с кодированием. С этой целью семейство GPT-4.1 имеет существенно улучшенные возможности diff по сравнению с предыдущими моделями GPT. Более того, хотя GPT-4.1 имеет высокую производительность при генерации различий любого формата с четкими инструкциями и примерами, мы открываем здесь исходный код одного рекомендуемого формата различий, на котором модель была тщательно обучена. Мы надеемся, что, в частности, для разработчиков, которые только начинают, это устранит большую часть догадок при самостоятельном создании различий.
Применить патч
Ниже приведен пример подсказки, которая правильно применяет наш рекомендуемый вызов инструмента.
APPLY_PATCH_TOOL_DESC = """This is a custom utility that makes it more convenient to add, remove, move, or edit code files. `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 """ APPLY_PATCH_TOOL = { "name": "apply_patch", "description": APPLY_PATCH_TOOL_DESC, "parameters": { "type": "object", "properties": { "input": { "type": "string", "description": " The apply_patch command that you wish to execute.", } }, "required": ["input"], }, }
Реализация эталона: apply_patch.py
Вот эталонная реализация инструмента apply_patch, которую мы использовали в рамках обучения модели. Вам нужно будет сделать его исполняемым и доступным как `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(" ") # ------------- 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(" ") 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: {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 = " ".join(next_ctx) raise DiffError( f"Invalid {'EOF ' if eof else ''}context at {index}: {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=" ".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(" ") 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 " ".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()
Другие эффективные форматы различий
Если вы хотите попробовать использовать другой формат различий, то в ходе тестирования мы обнаружили, что формат различий SEARCH/REPLACE, используемый в полиглот-тесте Эйдера, а также псевдо-XML-формат без внутреннего экранирования имеют высокие показатели успешности.
Эти форматы различий имеют два ключевых аспекта: (1) они не используют номера строк и (2) они предоставляют как точный код, который необходимо заменить, так и точный код, которым его следует заменить, с четкими разделителями между ними.
SEARCH_REPLACE_DIFF_EXAMPLE = """ path/to/file.py ``` >>>>>>> SEARCH def search(): pass ======= def search(): raise NotImplementedError() <<<<<<< REPLACE """ 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> """
Источник: cookbook.openai.com
Комментарии: