Endpoint OpenAI Responses yang dihosting sendiri

Nota

Pembantu hosting mandiri untuk titik akhir Respons OpenAI di .NET akan segera hadir.

Nota

Pembantu hosting mandiri untuk titik akhir Respons OpenAI saat ini tidak tersedia untuk Go.

Gunakan agent-framework-hosting-responses untuk mengonversi permintaan dan respons berformat OpenAI Responses pada endpoint milik aplikasi Anda. Server Anda memilih kerangka kerja web, rute, autentikasi, otorisasi, opsi permintaan, dan penyimpanan sesi.

pip install --pre agent-framework agent-framework-foundry agent-framework-hosting agent-framework-hosting-responses azure-identity

Sampel FastAPI adalah satu implementasi. Pembantu yang sama bekerja dengan Django, Flask, Starlette, Azure Functions, atau kerangka kerja lain.

Menghosting titik akhir agen

Sampel ini mengonversi permintaan ke nilai eksekusi Agent Framework, menerapkan daftar izin opsi yang ditentukan aplikasi, dan mempertahankan sesi yang diperbarui di bawah ID respons yang baru dibuat.

app = FastAPI()
state = AgentState(
    create_agent,
    session_store=FileSessionStore(SESSIONS_DIR / "snapshots"),
)

ALLOWED_REQUEST_OPTIONS = frozenset({"max_tokens", "reasoning"})


@app.post("/responses", response_model=None)
async def responses(body: dict[str, Any] = Body(...)) -> JSONResponse | StreamingResponse:  # noqa: B008
    """Handle one OpenAI Responses-shaped request."""
    try:
        run = responses_to_run(body)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc
    session_id, is_conversation_id = responses_session_id(body)
    conversation_id = session_id if is_conversation_id else None
    response_id = create_response_id()

    # App-specific policy: allow only the request options this route is willing
    # to honor. This denies tools, tool_choice, deployment/persistence fields,
    # and all other caller-supplied options by default. Your app decides which
    # options are allowed, altered, or denied.
    options = {key: value for key, value in run["options"].items() if key in ALLOWED_REQUEST_OPTIONS}
    options["reasoning"] = {"effort": "medium", "summary": "auto"}
    options_for_run = cast(Any, options)

    target = await state.get_target()
    lookup_id = session_id or response_id
    # An unknown id supplied through `conversation` becomes a new session here. Production apps
    # can choose to require a separate "create conversation" API instead.
    session = await state.get_or_create_session(lookup_id)
    if run["stream"]:
        stream = target.run(
            run["messages"],
            stream=True,
            session=session,
            options=options_for_run,
        )
        if not isinstance(stream, ResponseStream):
            raise HTTPException(status_code=500, detail="agent did not return a response stream")

        async def stream_events() -> AsyncIterator[str]:
            async for event in responses_from_streaming_run(
                stream,
                response_id=response_id,
                conversation_id=conversation_id,
            ):
                yield event
            # `agent.run(..., stream=True)` updates the session while the stream
            # is consumed/finalized. Persist the selected continuation only
            # after finalization.
            if conversation_id is not None:
                # A stable conversation id is a mutable head. Apps must ensure
                # only one caller advances it at a time; AgentState does not
                # serialize concurrent runs for the same id.
                await state.set_session(conversation_id, session)
            else:
                await state.set_session(response_id, session)

        return StreamingResponse(
            stream_events(),
            media_type="text/event-stream",
        )

    result = await target.run(
        run["messages"],
        session=session,
        options=options_for_run,
    )
    # `agent.run(...)` updates the session. Persist the selected continuation
    # only after the run completes.

AgentState menyelesaikan target dan memuat atau membuat sesi. Simpan sesi setelah eksekusi, atau setelah eksekusi streaming selesai, karena proses tersebut memperbaruinya.

Untuk aplikasi lengkap, termasuk definisi agen dan daftar izin opsi permintaan, lihat sampel Respons lokal.

Pahami konversi penggunaan respons

Untuk respons agen dan alur kerja, paket hosting mempertahankan objek OpenAI ResponseUsage asli yang valid SDK tidak berubah saat tersedia. Ini tidak menggabungkan penggunaan native Responses dengan Agent Framework UsageDetails.

Saat penggunaan asli tidak tersedia, paket dapat merekonstruksi penggunaan Respons dari bidang Kerangka Kerja Agen yang cocok secara semantik ini:

Nilai penggunaan Bidang Kerangka Kerja Agen
Token masukan input_token_count
Token keluaran output_token_count
Token input yang dibaca dari cache cache_read_input_token_count
Token input penulisan cache cache_creation_input_token_count
Token keluaran penalaran reasoning_output_token_count

Nilai nol eksplisit dipertahankan. Jika total_tokens tidak tersedia sementara jumlah input dan output tersedia, paket menghitungnya sebagai jumlah input ditambah output.

Jika penggunaan Kerangka Kerja Agen yang tersedia tidak lengkap atau tidak konsisten secara semantik dengan skema Respons, paket akan menghilangkan penggunaan. Tidak menebak, menyalin nilai dari satu penghitung ke penghitung lain, atau menggagalkan respons yang sebenarnya berhasil. Jumlah skalar yang salah bentuk tetap merupakan kesalahan.

Rekonstruksi ini sengaja bersifat lossy karena penggunaan Agent Framework tidak bergantung pada penyedia, sedangkan penggunaan OpenAI Responses memiliki struktur yang lebih kaya dan spesifik untuk penyedia. Penghitung khusus penyedia yang dilaporkan oleh agen yang dihosting, seperti penggunaan khusus Anthropic, oleh karena itu mungkin tidak muncul dalam respons yang diterima oleh aplikasi panggilan. Konversi ini tidak memberikan interoperabilitas antara versi SDK OpenAI yang berbeda yang berjalan dalam proses yang sama.

Menghosting titik akhir alur kerja

WorkflowState menyelesaikan alur kerja, tetapi aplikasi Anda bertanggung jawab atas penyimpanan titik pemeriksaan dan pemetaan dari ID respons ke titik pemeriksaan. Sampel ini memulihkan titik pemeriksaan yang dipilih oleh yang diotorisasi previous_response_id, lalu menyimpan kursor untuk respons berikutnya.

app = FastAPI()
state = WorkflowState(workflow_builder, cache_target=False)


@app.post("/responses", response_model=None)
async def responses(body: dict[str, Any] = Body(...)) -> JSONResponse:  # noqa: B008
    """Handle one OpenAI Responses-shaped request for the workflow."""
    try:
        run = responses_to_run(body)
    except ValueError as exc:
        raise HTTPException(status_code=400, detail=str(exc)) from exc

    # This sample demonstrates only Responses `previous_response_id`
    # continuation, so reject `conversation` instead of treating it as a
    # checkpoint cursor.
    previous_response_id, is_conversation_id = responses_session_id(body)
    if is_conversation_id:
        raise HTTPException(
            status_code=400,
            detail="This server supports previous_response_id continuation only; conversation is not implemented.",
        )
    response_id = create_response_id()

    target = await state.get_target()
    if previous_response_id and (checkpoint_cursor := checkpoint_cursor_store.get(previous_response_id)) is not None:
        # Restore first. Workflow.run does not allow `message` and
        # `checkpoint_id` in the same call.
        await target.run(
            checkpoint_id=checkpoint_cursor["checkpoint_id"],
            checkpoint_storage=checkpoint_storage_for(checkpoint_cursor["storage_id"]),
        )

    storage_id = response_id
    checkpoint_storage = checkpoint_storage_for(storage_id)
    result = await target.run(
        message=workflow_prompt_from_messages(run["messages"]),
        checkpoint_storage=checkpoint_storage,
    )

    latest = await checkpoint_storage.get_latest(workflow_name=target.name)
    if latest is not None:
        # Responses `previous_response_id` can point to any response id. Store
        # the current response id as the cursor for this workflow continuation.
        cursor = CheckpointCursor(checkpoint_id=latest.checkpoint_id, storage_id=storage_id)
        checkpoint_cursor_store.set_many({response_id: cursor})

    return JSONResponse(
        responses_from_run(
            response_from_workflow_result(result),
            response_id=response_id,
        )
    )

Penyimpanan berbasis file pada contoh ini digunakan untuk pengembangan lokal. Gunakan penyimpanan tahan lama saat replika dapat memulai ulang atau meluaskan skala.

Penting

Perlakukan previous_response_id dan conversation sebagai input yang tidak tepercaya. Autentikasi dan beri otorisasi kepada pemanggil sebelum menggunakan salah satu dari kedua nilai tersebut untuk memuat atau menyimpan sesi atau titik pemeriksaan. Bidang permintaan warisan conversation_id tidak digunakan lagi; gunakan bidang Respons conversation OpenAI sebagai gantinya.

Untuk format kawat yang lebih luas, lihat Titik akhir yang kompatibel dengan OpenAI.

Langkah berikutnya

Masuk lebih dalam: