Slutpunkter för OpenAI-svar på egen värd

Anmärkning

Hjälpverktyg för egenhosting av OpenAI Responses-slutpunkter i .NET kommer snart.

Anmärkning

Självbetjäningshjälpare för OpenAI-svarsslutpunkter är för närvarande inte tillgängliga för Go.

Använd agent-framework-hosting-responses för att konvertera begäranden och svar i OpenAI Responses-format på en ändpunkt som din applikation hanterar. Servern väljer webbramverk, väg, autentisering, auktorisering, alternativ för begäranden och sessionslagring.

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

FastAPI-exemplet är en implementering. Samma hjälparbetare arbetar med Django, Flask, Starlette, Azure Functions eller något annat ramverk.

Hysa en agentslutpunkt

Det här exemplet konverterar begäran till Agent Framework-körningsvärden, tillämpar en programdefinierad alternativlista och bevarar den uppdaterade sessionen under det nyligen skapade svars-ID:t.

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 löser målet och läser in eller skapar en session. Spara sessionen efter körningen, eller när en streamingkörning är klar, eftersom körningen uppdaterar den.

För den fullständiga applikationen, inklusive agentdefinitionen och tillåtslistan för alternativ för begäranden, se det lokala Responses-exemplet.

Förstå konvertering av svarsanvändning

För agent- och arbetsflödessvar bevarar värdpaketet ett SDK-giltigt internt OpenAI-objekt ResponseUsage som inte ändras när ett är tillgängligt. Den sammanfogar inte intern svarsanvändning med Agent Framework UsageDetails.

När intern användning inte är tillgänglig kan paketet rekonstruera svarsanvändningen från dessa semantiskt matchande Agent Framework-fält:

Användningsvärde Agent Framework-fält
Token för indata input_token_count
Token för utdata output_token_count
Indatatoken som läses från cache cache_read_input_token_count
Indatatoken för cacheskrivning cache_creation_input_token_count
Utdatatoken för resonemang reasoning_output_token_count

Explicita nollvärden bevaras. Om total_tokens saknas samtidigt som både antalet indata och utdata finns angivna, beräknar paketet det som summan av indata och utdata.

Om den tillgängliga Agent Framework-användningen är ofullständig eller semantiskt inkonsekvent med svarsschemat utelämnar paketet användning. Den gissar inte, kopierar en räknare till en annan eller misslyckas med ett annars lyckat svar. Ett felaktigt skalärt antal förblir ett fel.

Den här rekonstruktionen är avsiktligt förlustbringande eftersom användningen av Agent Framework är provider-neutral och användningen av OpenAI Responses har en rikare, providerspecifik struktur. Provider-specifika räknare som rapporteras av en hostad agent, till exempel Anthropic-specifik användning, kanske därför inte visas i svaret som tas emot av den anropande applikationen. Den här konverteringen ger inte samverkan mellan olika versioner av OpenAI SDK som körs i samma process.

Vara värd för en arbetsflödesslutpunkt

WorkflowState löser arbetsflödet, men ditt program äger kontrollpunktslagringen och mappningen från ett svars-ID till en kontrollpunkt. Det här exemplet återställer kontrollpunkten som valts av en auktoriserad previous_response_idoch sparar sedan en markör för nästa svar.

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

Exemplets filbaserade lagring är avsedd för lokal utveckling. Använd varaktig lagring när repliker kan starta om eller skala ut.

Important

Behandla previous_response_id och conversation som ej betrodda indata. Autentisera och auktorisera anroparen innan du använder värdet för att läsa in eller spara en session eller kontrollpunkt. Det äldre conversation_id fältet för begäran är inaktuellt. Använd fältet OpenAI-svar conversation i stället.

För det övergripande trådformatet, se OpenAI-kompatibla ändpunkter.

Nästa steg

Gå djupare: