Zelfgehoste eindpunten voor OpenAI Responses

Opmerking

Hulpmiddelen voor zelfhosting van OpenAI Responses-endpoints in .NET komen binnenkort beschikbaar.

Opmerking

Hulpmiddelen voor zelfhosting voor de Responses-endpoints van OpenAI zijn momenteel niet beschikbaar voor Go.

Gebruik agent-framework-hosting-responses om aanvragen en antwoorden in OpenAI Responses-indeling te converteren op een endpoint dat uw applicatie beheert. Uw server kiest het webframework, route, verificatie, autorisatie, aanvraagopties en sessieopslag.

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

Het FastAPI-voorbeeld is één implementatie. Dezelfde helpers werken met Django, Flask, Starlette, Azure Functions of een ander framework.

Een agenteindpunt hosten

In dit voorbeeld wordt de aanvraag geconverteerd naar uitvoerwaarden van Agent Framework, wordt een door de toepassing gedefinieerde optie allowlist toegepast en wordt de bijgewerkte sessie onder de zojuist gemaakte antwoord-id behouden.

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 bepaalt het doelobject en laadt of maakt een sessie. Sla de sessie op na de run, of nadat een streaming-run is voltooid, omdat de run deze bijwerkt.

Zie het lokale voorbeeld van antwoorden voor de volledige toepassing, inclusief de agentdefinitie en de allowlist met aanvraagopties.

Conversie van antwoordgebruik begrijpen

Voor reacties van agents en werkstromen behoudt het hostingpakket een SDK-geldig systeemeigen OpenAI-object ResponseUsage ongewijzigd wanneer er een beschikbaar is. Het voegt het systeemeigen gebruik van antwoorden niet samen met Agent Framework UsageDetails.

Wanneer systeemeigen gebruik niet beschikbaar is, kan het pakket het gebruik van reacties reconstrueren op basis van deze semantisch overeenkomende Agent Framework-velden:

Gebruikswaarde Agent Framework-veld
Invoertokens input_token_count
Uitvoertokens output_token_count
Invoertokens die uit de cache zijn gelezen cache_read_input_token_count
Invoertokens voor cacheschrijven cache_creation_input_token_count
Redenering van uitvoertokens reasoning_output_token_count

Expliciete nulwaarden blijven behouden. Als total_tokens ontbreekt, terwijl zowel het invoer- als het uitvoeraantal aanwezig is, berekent het pakket dit als invoer plus uitvoer.

Als het beschikbare Agent Framework-gebruik onvolledig of semantisch inconsistent is met het antwoordschema, wordt het gebruik van het pakket weggelaten. Het gokt niet, kopieert niet de ene teller naar de andere en laat een verder succesvol antwoord niet mislukken. Een onjuist gevormd scalaire telling blijft een fout.

Deze reconstructie is opzettelijk met verlies, omdat het gebruik van Agent Framework providerneutraal is en het gebruik van OpenAI Responses een rijkere, providerspecifieke structuur heeft. Providerspecifieke tellers die zijn gerapporteerd door een gehoste agent, zoals Anthropic-specifiek gebruik, worden daarom mogelijk niet weergegeven in het antwoord dat is ontvangen door de aanroepende toepassing. Deze conversie biedt geen interoperabiliteit tussen verschillende versies van de OpenAI SDK die in hetzelfde proces wordt uitgevoerd.

Een werkstroomeindpunt hosten

WorkflowState handelt de workflow af, maar uw toepassing beheert de opslag van controlepunten en de toewijzing van een respons-id aan een controlepunt. In dit voorbeeld wordt het controlepunt teruggezet dat door een geautoriseerde previous_response_id is geselecteerd, waarna een cursor voor de volgende respons wordt opgeslagen.

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

De bestandsopslag van het voorbeeld is bedoeld voor lokale ontwikkeling. Gebruik duurzame opslag wanneer replica's opnieuw kunnen worden opgestart of uitgeschaald.

Belangrijk

Behandelen previous_response_id en conversation als niet-vertrouwde invoer. Verifieer en autoriseren de beller voordat u een waarde gebruikt om een sessie of controlepunt te laden of op te slaan. Het verouderde conversation_id aanvraagveld is afgeschaft. Gebruik in plaats daarvan het veld OpenAI-antwoorden conversation .

Zie OpenAI-compatibele eindpunten voor de bredere draadindeling.

Volgende stappen 

Ga dieper in: