SPB Git

Repository Public

steering.md 12.7 KB 329 lines markdown

1# Mid-turn steering23> For the complete documentation index, see [llms.txt](/llms.txt). Markdown versions of documentation pages are available by appending `.md` to the page URL.45Mid-turn steering lets users add requirements or change direction without waiting for a response to finish.67Mid-turn steering is available with GPT-6 Astra (`gpt-6-astra`) over a8  WebSocket connection to the Responses API. GPT-5.6 and earlier models do not9  support steering.1011Steering does not rewrite output already sent to your application, undo earlier actions, or cancel tools that have already started.1213For connection setup and general transport behavior, see [WebSocket mode](https://developers.openai.com/api/docs/guides/websocket-mode). For exact event definitions, see the [Responses WebSocket events reference](https://developers.openai.com/api/reference/resources/responses/websocket-events).1415## Send a steering message1617Start a response with `response.create`. After receiving its `response.created` event, send `response.steer` on the same connection, using that response's ID as `previous_response_id`:1819```json20{21  "type": "response.steer",22  "previous_response_id": "resp_1",23  "input": "Keep the scope small enough for one developer to finish in two weeks."24}25```2627The event accepts only `type`, `previous_response_id`, and `input`. Set `input` to a string or a nonempty array of user messages with supported content types.2829The API acknowledges queued input with `response.steer.accepted`:3031```json32{33  "type": "response.steer.accepted",34  "sequence_number": 4,35  "steer": {36    "id": "steer_0123456789abcdef0123456789abcdef",37    "previous_response_id": "resp_1"38  }39}40```4142Acceptance means the input is queued, not that the model has acted on it. The API automatically creates a new response with your update unless it needs a [tool result or approval](#return-tool-results-or-approval) from your application.4344Before creating this automatic continuation, the server finishes the current output item and any hosted tool work already running. Keep reading events to receive the response with your update; do not send another `response.create`.4546If steering interrupts the original response, it ends with `response.incomplete` and `incomplete_details.reason: "steered"`. If the original response finishes normally first, it keeps its completed status and can still have a steering continuation.4748Automatic continuations inherit the original request settings. Token and tool-call limits apply separately to each response.4950## Run a complete example5152The .NET SDK does not provide a Responses WebSocket client, so a C# SDK variant is not available for this example.5354Update a project plan while it runs5556```javascript57// Set OPENAI_API_KEY before running this example.58// Install the SDK and WebSocket transport: npm install openai ws5960import OpenAI from "openai";61import { ResponsesWS } from "openai/resources/responses/ws";6263const client = new OpenAI();64const ws = new ResponsesWS(client, {65  handshakeTimeout: 10_000,66});67let initialResponseId = "";68let successorResponseId = "";69let timeout;7071try {72  const output = await new Promise((resolve, reject) => {73    timeout = setTimeout(() => {74      reject(new Error("Timed out waiting for the steered response."));75      ws.close();76    }, 120_000);77    ws.once("error", reject);78    ws.once("close", () => {79      reject(80        new Error("Connection closed before the steered response finished.")81      );82    });83    ws.on("event", (event) => {84      try {85        if (event.type === "response.created") {86          if (!initialResponseId) {87            initialResponseId = event.response.id;88            // Simulate a user adding instructions while the response runs.89            ws.send({90              type: "response.steer",91              previous_response_id: initialResponseId,92              input:93                "Keep the scope small enough for one developer to finish in two weeks.",94            });95          } else {96            successorResponseId = event.response.id;97          }98        } else if (99          ["response.steer.failed", "response.failed", "error"].includes(100            event.type101          )102        ) {103          reject(new Error(JSON.stringify(event)));104        } else if (105          event.type === "response.incomplete" &&106          (event.response.id !== initialResponseId ||107            event.response.incomplete_details?.reason !== "steered")108        ) {109          reject(new Error(JSON.stringify(event)));110        } else if (111          event.type === "response.completed" &&112          event.response.id === successorResponseId113        ) {114          let text = "";115          for (const item of event.response.output) {116            if (item.type !== "message") continue;117            for (const part of item.content) {118              if (part.type === "output_text") text += part.text;119            }120          }121          resolve(text);122        }123        // Acceptance only queues the input. Keep reading past the first response.124      } catch (error) {125        reject(error);126      }127    });128    ws.send({129      type: "response.create",130      model: "gpt-6-astra",131      reasoning: { effort: "medium" },132      input: "Draft a project plan for building a task-tracking app.",133    });134  });135  console.log(output);136} finally {137  clearTimeout(timeout);138  ws.close();139}140```141142```python143import asyncio144145from openai import AsyncOpenAI146147148async def main():149    client = AsyncOpenAI()150    initial_response_id = None151    successor_response_id = None152153    async with client.responses.connect() as connection, asyncio.timeout(120):154        await connection.response.create(155            model="gpt-6-astra",156            reasoning={"effort": "medium"},157            input="Draft a project plan for building a task-tracking app.",158        )159        async for event in connection:160            if event.type == "response.created":161                if initial_response_id is None:162                    initial_response_id = event.response.id163                    # Simulate a user adding instructions while the response runs.164                    await connection.response.steer(165                        previous_response_id=initial_response_id,166                        input="Keep the scope small enough for one developer to finish in two weeks.",167                    )168                else:169                    successor_response_id = event.response.id170            elif event.type in {"response.steer.failed", "response.failed", "error"}:171                raise RuntimeError(event.to_json())172            elif event.type == "response.incomplete":173                response = event.response174                if (175                    response.id != initial_response_id176                    or response.incomplete_details is None177                    or response.incomplete_details.reason != "steered"178                ):179                    raise RuntimeError(event.to_json())180            elif (181                event.type == "response.completed"182                and event.response.id == successor_response_id183            ):184                print(event.response.output_text)185                return186            # Acceptance only queues the input. Keep reading past the first response.187        raise RuntimeError("Connection closed before the steered response finished.")188189190asyncio.run(main())191```192193```ruby194require "async"195require "openai"196197client = OpenAI::Client.new198Sync do |task|199  task.with_timeout(120) do200    client.responses.connect(request_options: { timeout: 10 }) do |connection|201      connection.response.create(202        model: "gpt-6-astra", reasoning: { effort: "medium" },203        input: "Draft a project plan for building a task-tracking app."204      )205      state = {}206      while (event = connection.receive)207        case event208        when OpenAI::Responses::ResponseCreatedEvent209          response = event.response210          if !state[:initial_id]211            state[:initial_id] = response.id212            connection.send_event(213              type: "response.steer", previous_response_id: state[:initial_id],214              input: "Keep the scope small enough for one developer to finish in two weeks."215            )216          else217            state[:successor_id] = response.id218          end219        when OpenAI::Responses::ResponseSteerFailedEvent, OpenAI::Responses::ResponseFailedEvent, OpenAI::Responses::ResponsesServerEvent::ResponseWsError220          raise "Steering failed: #{event.to_json}"221        when OpenAI::Responses::ResponseIncompleteEvent222          response = event.response223          unless response.id == state[:initial_id] && response.incomplete_details&.reason.to_s == "steered"224            raise "Response incomplete: #{event.to_json}"225          end226        when OpenAI::Responses::ResponseCompletedEvent227          response = event.response228          next unless state[:successor_id] && response.id == state[:successor_id]229230          puts(response.output_text)231          state[:completed] = true232          break233        end234      end235      raise "Connection closed before the steered response finished" unless state[:completed]236    end237  end238end239```240241242The example sends the update after the first `response.created` event. In your application, send it when a user supplies an update. Use the continuation's ID for new steering once its `response.created` event arrives.243244## Return tool results or approval245246If the response needs a client tool result or approval, the API keeps the steering queued. Continue your normal tool or approval flow on the same connection.247248For example, the original response can complete with a call to `get_project_status`. The following payloads show only the relevant fields:249250```json251{252  "type": "response.completed",253  "response": {254    "id": "resp_1",255    "status": "completed",256    "output": [257      {258        "type": "function_call",259        "call_id": "call_project",260        "name": "get_project_status",261        "arguments": "{\"project\":\"task-tracker\"}"262      }263    ]264  }265}266```267268After the original response completes, the API sends `response.steer.pending` for accepted steering that still needs input. Its `required_input` field identifies the tool results or approvals the API needs before it can apply the update:269270```json271{272  "type": "response.steer.pending",273  "sequence_number": 12,274  "steer": {275    "id": "steer_0123456789abcdef0123456789abcdef",276    "previous_response_id": "resp_1"277  },278  "reason": "waiting_for_required_input",279  "required_input": [280    {281      "type": "function_call_output",282      "call_id": "call_project",283      "name": "get_project_status"284    }285  ]286}287```288289Return the required input with `response.create` on the same connection, setting `previous_response_id` to `resp_1`. Do not repeat the accepted steering. An explicit `response.create` uses its own tools, instructions, and other settings.290291The comments in this JSONC example show where the server adds the queued update:292293```jsonc294{295  "type": "response.create",296  "model": "gpt-6-astra",297  "previous_response_id": "resp_1",298  "input": [299    // The server implicitly prepends your accepted steer here:300    // "Keep the scope small enough for one developer to finish in two weeks."301    {302      "type": "function_call_output",303      "call_id": "call_project",304      "output": "Design is complete. Development has not started.",305    },306    {307      "role": "user",308      "content": "Show me the updated plan before starting any work.",309    },310  ],311}312```313314You do not need to wait for `response.steer.pending` before returning tool results. If the server has already received a matching `response.create`, it can proceed without sending this notification first.315316## Handle failures and disconnects317318`response.steer.failed` means the API did not apply the input through steering and will not apply it automatically later. The event returns the original `input` and `previous_response_id` under `steer`, with an `error` object describing the failure.319320Track accepted submissions by `steer.id`. A later failure uses the same ID.321322Common error codes:323324- `invalid_input`: Use only the supported event fields and user message input.325- `steering_not_supported`: The model, request parameters, or both may be incompatible with steering.326- `response_not_found`: The target response must still be available on the same WebSocket connection.327- `too_many_pending_steers`: Too much steering input is pending. Return any required tool results or approvals using `response.create`; otherwise, wait for the automatic continuation before submitting more. Do not resend already accepted steering.328329Queued steering input exists only on the current connection; it isn't stored with the original response. Record the steering inputs you send, and compare them with response events and history before replaying them. Do not assume pending steering survived the disconnect. See [WebSocket recovery guidance](https://developers.openai.com/api/docs/guides/websocket-mode#reconnect-and-recover).
Files main
Menu

Theme