Hostujte sami koncové body rozhraní Responses OpenAI

Poznámka:

Pomocné nástroje pro vlastní hostování koncových bodů OpenAI Responses v .NET budou brzy dostupné.

Poznámka:

Pomocné nástroje pro vlastní hostování koncových bodů Responses od OpenAI nejsou v současnosti pro Go k dispozici.

Použijte agent-framework-hosting-responses k převodu požadavků a odpovědí ve formátu Responses od OpenAI v koncovém bodě, který vlastní vaše aplikace. Váš server zvolí webovou architekturu, trasu, ověřování, autorizaci, možnosti požadavků a úložiště relací.

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

Ukázka FastAPI je jednou implementací. Stejné pomocné funkce fungují s Django, Flask, Starlette, Azure Functions nebo jiným frameworkem.

Hostování koncového bodu agenta

Tato ukázka převede požadavek na hodnoty běhu v rámci Agent Framework, použije seznam povolených možností definovaný aplikací a uloží aktualizovanou relaci pod nově vytvořeným ID odpovědi.

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 vyřeší cíl a načte nebo vytvoří relaci. Uložte relaci po spuštění nebo po dokončení streamování, protože spuštění ji aktualizuje.

Úplnou aplikaci, včetně definice agenta a seznamu povolených voleb požadavku, najdete v ukázce Local Responses.

Vysvětlení převodu využití odpovědí

Pro odpovědi agenta a pracovního postupu zachová hostitelský balíček platný nativní objekt OpenAI ResponseUsage sady SDK beze změny, pokud je k dispozici. Nespojuje nativní použití Responses s Agent Framework UsageDetails.

Pokud není k dispozici nativní využití, může balíček rekonstruovat využití odpovědí z těchto sémanticky odpovídajících polí agenta Framework:

Hodnota využití Pole rámce agenta
Vstupní tokeny input_token_count
Výstupní tokeny output_token_count
Vstupní tokeny pro čtení mezipaměti cache_read_input_token_count
Vstupní tokeny pro zápis do mezipaměti cache_creation_input_token_count
Odůvodnění výstupních tokenů reasoning_output_token_count

Explicitní nulové hodnoty se zachovají. Pokud total_tokens chybí, zatímco jsou uvedeny počty vstupů i výstupů, balíček jej vypočítá jako součet počtu vstupů a výstupů.

Pokud jsou dostupné údaje o využití v Agent Framework neúplné nebo sémanticky nekonzistentní se schématem Responses, balíček údaje o využití vynechá. Nehádá, nekopíruje hodnotu jednoho čítače do jiného ani nezpůsobí selhání jinak úspěšné odpovědi. Poškozený skalární počet zůstává chybou.

Tato rekonstrukce je záměrně ztrátová, protože použití Agent Frameworku je nezávislé na poskytovateli a použití OpenAI Responses má bohatší, pro poskytovatele specifickou strukturu. Čítače specifické pro poskytovatele, které hlásí hostovaný agent, například metriky využití specifické pro Anthropic, se proto nemusí objevit v odpovědi, kterou obdrží volající aplikace. Tento převod neposkytuje interoperabilitu mezi různými verzemi sady OpenAI SDK spuštěnou ve stejném procesu.

Hostování koncového bodu pracovního postupu

WorkflowState zpracuje workflow, ale za úložiště kontrolních bodů a mapování mezi ID odpovědi a kontrolním bodem odpovídá vaše aplikace. Tato ukázka obnoví kontrolní bod vybraný autorizovaným previous_response_iduživatelem a uloží kurzor pro další odpověď.

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,
        )
    )

Úložiště ukázkové aplikace založené na souborech je určené pro místní vývoj. Trvalé úložiště používejte, když se repliky mohou restartovat nebo horizontálně škálovat.

Důležité

Považujte previous_response_id a conversation za nedůvěryhodný vstup. Před použitím kterékoli z hodnot k načtení nebo uložení relace či kontrolního bodu ověřte a autorizujte volajícího. Starší conversation_id pole požadavku je zastaralé. Místo toho použijte pole Odpovědi conversation OpenAI.

Širší formát drátu najdete v tématu Koncové body kompatibilní s OpenAI.

Další kroky

Jděte hlouběji: