SPB Git forge

spb/doc-api

Public
2commits 1branches 0releases
15.7 MBsize
maindefault branch
13 days agolast push
Python 88.3% TypeScript 7.6% Shell 4.1%
7.7 KB · 131 lines python
Raw Blame History
1"""Smoke tests for the OpenAI Responses API family (responses, conversations, input_tokens, compact, background).23Cheap: gpt-5.4-nano, "Reply with OK.", max_output_tokens <= 32 (~$0.001 per full run). Compaction is the most expensive4call (~$0.0003) and is gated behind RUN_EXPENSIVE_TESTS.5"""6from __future__ import annotations78import json9import time1011import pytest1213PING = "Reply with OK."141516def _text(resp: dict) -> str:17    return "".join(c.get("text", "") for i in resp["output"] if i["type"] == "message" for c in i["content"] if c["type"] == "output_text")181920@pytest.fixture(scope="module")21def stored_response(openai, models):22    st, body, _ = openai("POST", "/v1/responses", {"model": models["openai"], "input": PING, "max_output_tokens": 32}, est_cost_usd=0.00002, note="test_responses minimal")23    assert st == 200, body24    yield body25    openai("DELETE", f"/v1/responses/{body['id']}", note="test_responses cleanup")262728def test_minimal_response_shape(stored_response):29    r = stored_response30    assert r["object"] == "response" and r["status"] == "completed"31    assert "OK" in _text(r)32    usage = r["usage"]33    assert usage["total_tokens"] == usage["input_tokens"] + usage["output_tokens"]34    assert "reasoning_tokens" in usage["output_tokens_details"] and "cached_tokens" in usage["input_tokens_details"]35    # fields the docs do not list but the API returns (2026-09-18)36    for extra in ("billing", "tool_usage"):37        assert extra in r, f"{extra} disappeared from the live Response object"383940def test_input_tokens_matches_usage(openai, models, stored_response):41    st, body, _ = openai("POST", "/v1/responses/input_tokens", {"model": models["openai"], "input": PING}, note="test_responses input_tokens")42    assert st == 200 and body["object"] == "response.input_tokens"43    assert body["input_tokens"] == stored_response["usage"]["input_tokens"]444546def test_retrieve_and_input_items(openai, stored_response):47    rid = stored_response["id"]48    st, body, _ = openai("GET", f"/v1/responses/{rid}", note="test_responses retrieve")49    assert st == 200 and body["id"] == rid50    st, items, _ = openai("GET", f"/v1/responses/{rid}/input_items?limit=5", note="test_responses input_items")51    assert st == 200 and items["object"] == "list" and items["data"][0]["type"] == "message"525354def test_streaming_event_sequence(openai, models):55    st, lines, _ = openai("POST", "/v1/responses", {"model": models["openai"], "input": PING, "max_output_tokens": 32, "stream": True}, stream=True, est_cost_usd=0.00002, note="test_responses stream")56    assert st == 20057    types, seqs = [], []58    for line in lines:59        if line.startswith("data:"):60            d = json.loads(line[5:])61            types.append(d["type"])62            seqs.append(d["sequence_number"])63    assert types[0] == "response.created" and types[-1] == "response.completed"64    assert "response.output_text.delta" in types and "response.output_item.done" in types65    assert seqs == list(range(len(seqs)))666768def test_previous_response_id_chain(openai, models, stored_response):69    st, body, _ = openai("POST", "/v1/responses", {"model": models["openai"], "input": "Reply with OK again.", "previous_response_id": stored_response["id"], "max_output_tokens": 32}, est_cost_usd=0.00002, note="test_responses chain")70    assert st == 200 and body["previous_response_id"] == stored_response["id"]71    assert body["usage"]["input_tokens"] > stored_response["usage"]["input_tokens"]  # prior turn re-billed72    openai("DELETE", f"/v1/responses/{body['id']}", note="cleanup")737475def test_store_false_is_not_retrievable(openai, models):76    st, body, _ = openai("POST", "/v1/responses", {"model": models["openai"], "input": PING, "max_output_tokens": 32, "store": False}, est_cost_usd=0.00002, note="test_responses store=false")77    assert st == 200 and body["store"] is False78    st, err, _ = openai("GET", f"/v1/responses/{body['id']}", note="test_responses retrieve unstored")79    assert st == 404808182def test_structured_output_json_schema(openai, models):83    schema = {"type": "object", "properties": {"answer": {"type": "string"}}, "required": ["answer"], "additionalProperties": False}84    st, body, _ = openai("POST", "/v1/responses", {"model": models["openai"], "input": PING, "max_output_tokens": 32, "store": False,85                                                    "text": {"format": {"type": "json_schema", "name": "ok", "strict": True, "schema": schema}}}, est_cost_usd=0.00003, note="test_responses json_schema")86    assert st == 200 and body["text"]["format"]["type"] == "json_schema"87    assert "answer" in json.loads(_text(body))888990def test_validation_errors(openai, models):91    st, err, _ = openai("POST", "/v1/responses", {"model": models["openai"], "input": PING, "max_output_tokens": 8}, note="test_responses max_output_tokens<16")92    assert st == 400 and err["error"]["code"] == "integer_below_min_value"93    st, err, _ = openai("POST", "/v1/responses", {"model": models["openai"], "input": PING, "max_output_tokens": 16, "conversation": "conv_x", "previous_response_id": "resp_x"}, note="test_responses mutually exclusive")94    assert st == 400 and err["error"]["code"] == "mutually_exclusive_parameters"959697def test_background_create_cancel(openai, models):98    st, body, _ = openai("POST", "/v1/responses", {"model": models["openai"], "input": PING, "max_output_tokens": 32, "background": True}, est_cost_usd=0.00002, note="test_responses background")99    assert st == 200 and body["status"] in ("queued", "in_progress", "completed")100    st, c, _ = openai("POST", f"/v1/responses/{body['id']}/cancel", note="test_responses cancel")101    # Race: a tiny background response may already be completed → 400 "Cannot cancel a completed response." (live 2026-09-18)102    assert (st == 200 and c["status"] in ("cancelled", "completed")) or (st == 400 and "completed" in c["error"]["message"])103    for _ in range(10):104        st, p, _ = openai("GET", f"/v1/responses/{body['id']}", note="test_responses poll")105        if p["status"] not in ("queued", "in_progress"):106            break107        time.sleep(1)108    assert p["status"] in ("cancelled", "completed")109    openai("DELETE", f"/v1/responses/{body['id']}", note="cleanup")110111112def test_conversations_lifecycle(openai, models):113    st, conv, _ = openai("POST", "/v1/conversations", {"metadata": {"atlas": "test"}}, note="test_responses conversation create")114    assert st == 200 and conv["object"] == "conversation"115    cid = conv["id"]116    st, items, _ = openai("POST", f"/v1/conversations/{cid}/items", {"items": [{"type": "message", "role": "user", "content": [{"type": "input_text", "text": PING}]}]}, note="items create")117    assert st == 200 and items["data"][0]["type"] == "message"118    st, resp, _ = openai("POST", "/v1/responses", {"model": models["openai"], "conversation": cid, "input": "Reply with OK once more.", "max_output_tokens": 32}, est_cost_usd=0.00002, note="response with conversation")119    assert st == 200 and resp["conversation"]["id"] == cid120    st, listed, _ = openai("GET", f"/v1/conversations/{cid}/items?order=asc", note="items list")121    assert st == 200 and [i["type"] for i in listed["data"]] == ["message", "message", "message"]122    st, deleted, _ = openai("DELETE", f"/v1/conversations/{cid}", note="conversation delete")123    assert st == 200 and deleted["deleted"] is True124125126@pytest.mark.run_expensive_tests127def test_compaction_endpoint(openai, models):128    st, body, _ = openai("POST", "/v1/responses/compact", {"model": models["openai"], "input": [{"role": "user", "content": PING}, {"role": "assistant", "content": "OK"}, {"role": "user", "content": "Reply with OK again."}]}, est_cost_usd=0.0003, note="test_responses compact")129    assert st == 200 and body["object"] == "response.compaction"130    assert any(i["type"] == "compaction" and i.get("encrypted_content") for i in body["output"])131