Главная / Статьи / Понимание элементов и сообщений в API ответов OpenAI

Понимание элементов и сообщений в API ответов OpenAI

Объясняется, как API Responses от OpenAI преобразует результаты работы модели в элементы вместо сообщений, и почему это изменение имеет значение для вызова инструментов и рабочих процессов с агентами.

1735 слов

Дополнение сообщений в чате: построено вокруг сообщений

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

response = client.chat.completions.create(
    model="...",
    messages=[
        {
            "role": "user",
            "content": "Explain RAG"
        }
    ]
)

Вы отправляете список сообщений, и модель возвращает следующее сообщение ассистента. Всё довольно просто. Однако API Responses с самого начала отличается по своей структуре.

response = client.responses.create(
    model="...",
    input="Explain RAG"
)

На первый взгляд это может показаться просто другим концом точки доступа, где input заменяет messages. Но настоящая разница заключается в основной абстракции, используемой каждым API. Формат дополнения сообщений в чате основан на сообщениях, тогда как API Responses организован вокруг элементов и ответов. Когда в игру вступают инструменты и агенты, эта разница становится гораздо более значимой.

Разговор в режиме Chat Completions представляет собой просто массив объектов сообщений.

messages = [
    {
        "role": "system",
        "content": "Act as a helpful AI assistant."
    },
    {
        "role": "user",
        "content": "What is a vector database?"
    }
]

Каждое сообщение содержит роль и определенный контент.

Типичные роли, которые вы можете увидеть, следующие:

  • system
  • user
  • assistant

Вы можете представить себе ход разговора следующим образом:

Messages (list)-> Model -> Assistant Message

Модель принимает текущий ход разговора и генерирует следующее сообщение ассистента. Минимальный цикл разговора выглядит так:

# Human message appended to the messages list
messages.append({
    "role": "user",
    "content": user_input
})
response = client.chat.completions.create(
    model="...",
    messages=messages
)
# AI message appended to the messages list
messages.append(
    response.choices[0].message
)

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

User Message (str)->
Message History (list[dict])->
Model (llm)->
Assistant Message (str)->
Message History (list[dict])

Для генерации обычного текста и типичных сценариев чатов такая конфигурация вполне подходит. Однако её ограничения становятся заметными, когда приложению на основе LLM необходимо делать что-то большее, чем просто вести диалог.

Приложения на основе LLM — это не только чат-приложения

Возьмём такую просьбу:

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

Чтобы выполнить эту просьбу, модели необходимо инициировать поиск в интернете и подключиться к сервису электронной почты.

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

User Request ->
Model ->
Web Search ->
Search Results ->
Summarize (Model)->
Send Mail ->
Final Response

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

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

Эту схему уже нельзя свести к следующему:

Messages (list)-> Model -> Assistant Message

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

Не каждый результат работы модели — это сообщение

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

Возьмем в пример следующее:

Function Call
Name: get_weather
Arguments:
{
    "city": "Bengaluru"
}

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

На практике модель может генерировать как минимум два разных вида вывода во время работы:

  1. Вызов функции
  2. Сообщение

Объединение этих двух видов вывода под одним названием «сообщение ассистента» не отражает того, что на самом деле происходит во время выполнения. Именно это несоответствие является основной проблемой проектирования, которую предполагается решить с помощью API Responses.

API Responses: иная абстракция

Вместо того чтобы быть ориентированной на обмен сообщениями, API Responses построена вокруг концепции ответа, который может объединять в себе несколько элементов вывода.

response = client.responses.create(
    model="...",
    input="Explain LangGraph"
)
print(response.output)
# response.output is a list of output items.

Для простого текстового запроса вывод может состоять всего из одного сообщения. Однако в случае использования агентов или инструментов ответ может включать несколько разных типов элементов. Упрощённое представление этой структуры выглядит следующим образом:

Response
| Reasoning Item
| Function Call Item
| Message Item

В этой модели сообщение становится лишь одним из нескольких возможных типов вывода, а не всем содержанием ответа.

Разница:

Разница:

Messages (list)-> Model -> Assistant Message

Дополнение сообщений в чате

Input -> Model -> Response

Response:
| Output Item
| Output Item
| Output Item

API Responses

В функции Chat Completions моделируется сам диалог, тогда как API Responses представляет выполнение модели в виде ответа, состоящего из элементов вывода. Для простого дополнения текста это различие практически не имеет значения. Оно становится значимым, когда в игру вступают инструменты, модели с способностью к рассуждениям или агентные рабочие процессы.

Сообщения против элементов

Настоящая разница между этими двумя API проявляется в том, как каждый из них структурирует свой ответ.

В функции Chat Completions всё сосредоточено вокруг сообщения.

print(response.choices[0].message.content)
# The generated text is inside the assistant message.
# response
# | choices
#     | message
#         | content

API Responses организует свой вывод иначе.

print(response.output)
# A simplified structure:
# response
# | output
#   | reasoning
#   | function_call
#   | message
# The important difference is that output is not a list of messages,
# but output items.

Сообщение — это лишь один тип элемента; вызов функции — другой тип; кроме того, модели, поддерживающие рассуждения, могут также генерировать элементы, отражающие процесс рассуждений. Это меняет представление о том, что на самом деле возвращает модель.

Chat Completions
Model Output = Assistant Message

Responses API
Model Output = List of Output Items

- The structure which is useful for tool calling.

Вызовы инструментов как элементы вывода

Возьмем простую функцию, которая сообщает погоду (классический пример, используемый везде).

def get_weather(city: str):
    return f"The weather in {city} is 28°C"
# The weather is ofcoure hardcoded.

Эту функцию можно предоставить модели в виде определения инструмента.

tools = [
    {
        "type": "function",
        "name": "get_weather",
        "description": "Get the current weather for a city",
        "parameters": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string"
                }
            },
            "required": ["city"]
        }
    }
]

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

response = client.responses.create(
    model="...",
    tools=tools,
    input="What is the weather in Bengaluru?"
)

Далее у модели есть два возможных пути дальнейших действий.

Option 1: Generate a message
Option 2: Call get_weather

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

Function Call Item
name: get_weather
arguments:
{
    "city": "Bengaluru"
}

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

for item in response.output:
    if item.type == "function_call":
        print(item.name)
        print(item.arguments)

При его выполнении получается:

get_weather
{"city":"Bengaluru"}

На этом этапе модель ещё не дала окончательного ответа, она лишь попросила выполнить определённое действие; само выполнение функции лежит на вашем приложении.

result = get_weather("Bengaluru")

Этот результат затем должен быть возвращён модели.

Результат вызова функции

Результат работы инструмента представляется с помощью элемента типа function_call_output.

tool_output = {
    "type": "function_call_output",
    "call_id": item.call_id,
    "output": result
}

Поле call_id связывает этот результат с конкретным вызовом функции, который его запросил.

Function Call
| call_id: call_123
Application Executes Tool
Function Call Output
| call_id: call_123

Такая связь становится необходимой, когда одновременно запускается несколько инструментов, например запрос о погоде в двух разных городах.

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

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

User Input ->
Model ->
Response Output Items ->
Check for Function Calls ->
Execute Functions ->
Create Function Call Outputs ->
Model ->
Final Response

Вот пример того, как это выглядит в коде.

response = client.responses.create(
    model="...",
    input=user_input,
    tools=tools
)
while True:
    function_calls = [
        item
        for item in response.output
        if item.type == "function_call"
    ]
    if not function_calls:
        break
    tool_outputs = []
    for call in function_calls:
        result = execute_tool(
            call.name,
            call.arguments
        )
        tool_outputs.append({
            "type": "function_call_output",
            "call_id": call.call_id,
            "output": result
        })
    response = client.responses.create(
        model="...",
        previous_response_id=response.id,
        input=tool_outputs,
        tools=tools
    )

Этот цикл продолжает повторяться до тех пор, пока модель продолжает возвращать результаты вызовов функций. Как только она прекращает запрашивать результаты инструментов, в ответе остаётся окончательный результат модели.

Model ->
Function Call ->
Tool Result ->
Model ->
Function Call ->
Tool Result ->
Model ->
Message

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

Заключение

API Responses — это не просто переименованный интерфейс Chat Completions. Он предоставляет структуру, которая более точно отражает способ работы современных приложений на основе больших языковых моделей, когда задействованы инструменты, логические рассуждения и различные типы вывода. Chat Completions по-прежнему остается хорошим выбором для простых сценариев общения. Но как только ваш рабочий процесс начинает приближаться к агентному подходу, API Responses становится более подходящим решением. Сообщения отвечают за ведение диалога; элементы — за выполнение задач. В этом вся суть.

Связанные материалы