AnnouncementsIntroducing Arkoren

    Menu

    Drivers

    Configure hosted OpenAI or a compatible server and compare the driver's two API modes.

    What's on this page
    1. Choose an API mode using the driver's media, reasoning, tool, and schema support.
    2. Configure credentials, gateways, retries, and embedding batches without changing facade calls.
    3. Use request hooks, a caller-owned SDK client, or optional verified response notifications.
    On this page

    Use the OpenAI driver for hosted OpenAI models or a server with a compatible API. Choose the API mode and model together: a server can accept text requests without supporting your tools, schemas, or media inputs. The matrix below shows what Arkoren translates in each mode, so you can check those requirements before connecting.

    The driver handles the vendor’s wire protocol and maps its errors onto the contract’s sentinels. Defaults, request validation, agents, telemetry, and events belong to the facade; your calling code uses the same contract types.

    There is one vendor driver today, OpenAI. Compatible servers use that driver, subject to their API and model limitations. There is no memory driver; tests use the scripted llmtest.Fake (Testing Language Models). Choosing a Driver compares drivers across services.

    Compatibility Matrix

    Compare the two API modes of the shipped OpenAI driver. These are translation capabilities exposed by Arkoren, not guarantees that every model or compatible server accepts every request. Ollama, vLLM, and gateways use this same driver; check their selected API and model rather than assuming OpenAI feature parity. The scripted fake is for application tests, not a separate provider protocol.

    Driver and mode Streaming Tool calls Structured output Image input Document input Reasoning history Embeddings
    OpenAI: Responses Yes Yes Yes Yes Yes Yes
    OpenAI: Chat Completions Yes Yes Yes Yes No No Yes

    Responses is the default mode. Chat Completions rejects document parts with llm.ErrUnsupported and drops reasoning history, though it still sends the requested reasoning effort. Responses restores reasoning items only when their signature contains the item ID and encrypted content. Structured output uses a JSON Schema response format with no prompt-based fallback if the server rejects it. Embeddings use the separate embeddings endpoint and model in either mode. Select a model and server that support your required inputs, tools, and schemas.

    OpenAI

    The openaillm driver speaks to OpenAI through its Responses API, and through Chat Completions for servers that only implement that. Choose it for OpenAI’s models, or for a compatible server such as Ollama, vLLM, or a gateway. It handles text, images, PDFs, tool calls, reasoning, structured output, and embeddings.

    Installation

    Install the OpenAI driver from your application’s module directory:

    go get arkoren.dev/core arkoren.dev/driver/openai

    Connecting

    For hosted OpenAI, obtain an API key and choose a model your account can access. Set OPENAI_API_KEY outside source control. Requests can incur provider charges; this guide does not require making a live call. With your selected ID in modelID, construct the connection and facade:

    import (
    	"arkoren.dev/core/service/llm"
    	"arkoren.dev/driver/openai/openaillm"
    )
    
    driver, err := openaillm.New() // OPENAI_API_KEY, OPENAI_BASE_URL from the environment
    
    model := llm.New(driver, modelID)

    Handle the construction error before using driver. Set OPENAI_API_KEY before calling the hosted service and choose a model your account can access. New does no I/O; connection and credential failures appear on the first request. Follow the overview to get your first answer. Register Ping as a health check if the server supports listing models.

    Configuration

    If your organization routes calls through an OpenAI-compatible gateway, keep the same calling code and change the connection. Here key is the gateway credential loaded by your application:

    driver, err := openaillm.New(func(driver *openaillm.Builder) {
    	driver.APIKey(key)
    	driver.BaseURL("https://llm-gateway.internal/v1")
    })

    You can also set OPENAI_BASE_URL without changing source. Keep SDK retries off unless you deliberately want retry policy in the connection rather than your application. They default to zero additional attempts; MaxRetries(n) enables up to n SDK retries. Use a request context with a deadline to bound the whole call. If you supply HTTPClient, its timeout and transport remain your responsibility; NewFrom retains the native client’s own retry policy. New reads OPENAI_API_KEY, OPENAI_BASE_URL, OPENAI_ORG_ID, and OPENAI_PROJECT_ID through the SDK unless explicitly configured. Servers without authentication need no API key. Empty API selects Responses; empty strict mode selects its mode-specific default. These empty values are not equal to the named constants. Unknown API modes and negative retry or embedding batch-size settings fail construction.

    Embeddings use batches of at most 2048 inputs by default; zero restores that default. Results are ordered by the response index and input-token usage is summed across batches. Purpose is ignored by this driver. Use request hooks for native parameters or reuse an SDK client for existing connection policy.

    Generating Replies

    With the model above, follow generating replies to receive text or streaming text to display it as it arrives. For vectors, pass the same driver to the embedding setup; the embedding model is separate from the chat model. No driver Start or Stop call is required.

    Mode Limits

    Use the default Responses mode when the server supports it. If it only offers Chat Completions, add driver.API(openaillm.ChatCompletions) to the construction callback. Documents and reasoning do not have the same support. The Ollama workflow shows local setup and mode selection. Ping needs a working GET /models endpoint and reads one page. A server without that endpoint fails readiness even if generation works.

    By default, Responses initializes store: false and include: ["reasoning.encrypted_content"] before the native hook. These are request defaults, not enforced storage restrictions: a hook can override either field, and an explicit Store = openai.Bool(true) reaches both generation and streaming requests. Neither mode guarantees that the server avoids logging or retaining data. Review your hooks, gateway configuration, and provider policy before sending private prompts or tool results.

    A compatible server must accept the parameters your hook leaves in the request. Reasoning signatures retain <item id>:<encrypted content> for replay with your saved conversation. Reasoning without that signature is skipped. Document parts become input_file data URLs. Tool calls use call_id; tool-result error flags have no provider field, so their content is sent unchanged.

    Chat Completions sends single text parts as plain strings for older servers and requests stream usage. It rejects documents with llm.ErrUnsupported, drops reasoning history/output, and sends only reasoning effort. For conformance tests in this mode, skip those checks with llmtest.WithoutDocuments() and llmtest.WithoutReasoning().

    In both modes, maximum tokens map to the corresponding provider output limit. Reasoning Off sends none, Low/Medium/High use their names, and Default omits the setting. Input-token usage includes cached tokens; output usage includes reasoning tokens. Unreported counts remain zero. Stop reasons remain successful single-turn replies: tool calls, exhausted output limits, refusals, and content filters have distinct portable reasons. The agent loop interprets those reasons as described in Reading Outcomes.

    Stopping iteration cancels the HTTP request. A stream missing its terminal event fails with llm.ErrUnavailable wrapping io.ErrUnexpectedEOF.

    Strict Schemas

    Strict schema rewriting defaults on for Responses and off for Chat Completions. To disable it explicitly, set StrictSchemas(false) in the construction callback. Strict rewriting recursively marks every property required, permits null for formerly optional properties, and sets additionalProperties: false. It traverses properties, items, composition branches, $defs, and definitions. Open maps without properties remain unchanged; they do not satisfy OpenAI’s strict-object requirements. A compatible server’s acceptance depends on its own schema support. Turning rewriting off does not guarantee that a server accepts open maps or structured output.

    A null optional value decodes to Go zero, which typed validation treats as absent. With strict mode off, schemas remain unchanged and are sent with strict: false.

    Tool and output schemas must have JSON-object roots in either mode. JSON null, arrays, and scalar roots fail locally with llm.ErrInvalidRequest before the native hook or an HTTP request. Strict mode does not replace the application’s validation of model output or tool arguments.

    Structured output uses text.format in Responses or response_format in Chat Completions, with type json_schema. Unsupported formats return llm.ErrUnsupported; there is no prompt-based fallback. Use Structured Output to declare and validate the Go result.

    Provider Failures

    The SDK’s *openai.Error stays available through errors.As. The driver maps 429 to llm.ErrRateLimited, server failures to ErrUnavailable, missing models to ErrNotFound, context-limit failures to ErrContextLength, and policy refusals to ErrRefused. Other client failures normally match ErrInvalidRequest.

    A 400/422 naming structured-output fields while an output schema was sent can match ErrUnsupported, except invalid_json_schema, which remains ErrInvalidRequest. A failed Responses body/stream event uses its error code; unknown body codes also become ErrInvalidRequest. Do not treat an invalid schema as evidence that the server lacks structured output.

    Response Notifications

    This setup is optional; ordinary generation, streaming, and embeddings do not require a webhook endpoint. When your application creates background work through OpenAI’s provider API, receive its response lifecycle notifications with openaiwebhook from the same module. This adapter neither starts background work nor changes openaillm requests. Copy the endpoint’s whsec_ signing secret from OpenAI’s webhook settings, not your API key.

    With imports arkoren.dev/core/http/webhook and arkoren.dev/driver/openai/openaiwebhook, supply your accept function of type webhook.AcceptFunc[openaiwebhook.Response]:

    source, err := openaiwebhook.New(signingSecret)
    if err != nil {
    	return err
    }
    handler, err := webhook.Handler(source, func(receiver *webhook.Builder[openaiwebhook.Envelope]) {
    	receiver.Handle(openaiwebhook.ResponseCompleted, accept)
    })
    if err != nil {
    	return err
    }
    r.Post("/webhooks/openai", handler)

    Here r is your application router; check both construction errors before registering the handler. Configure its public HTTPS URL and event subscriptions in OpenAI. Core acknowledges after acceptance succeeds; the webhook guide covers queueing and receipt policy. The adapter also defines failed, canceled, and incomplete events. These are response references, not generated output: retrieve the response in your handler or worker.

    The official SDK verifies the raw body and a five-minute past/future timestamp window. Keep clocks synchronized and use deduplication to reduce replays within that window. Delivery.ID is the signed webhook-id; Envelope.ID is the separate JSON event ID. For payloads and failure behavior, see the OpenAI webhook reference.

    New requires a canonical whsec_ base64 secret with at least 24 decoded bytes. Invalid configuration wraps webhook.ErrInvalid; invalid authentication matches both openaiwebhook.ErrInvalidSignature and webhook.ErrRejected (400 by default). Authenticated malformed envelopes or bound response references return 503. Bound events need data.id and a positive Unix-seconds created_at; an optional nonempty object must be event. Authenticated unbound events receive 200 without event-specific decoding.

    No response is fetched automatically. ResponseCanceled uses native event key response.cancelled; failed, canceled, and incomplete payloads remain thin references, not output snapshots or failure reasons. OpenAI’s webhook delivery policy describes retries for up to 72 hours, but that is provider policy, not an exactly-once receiver guarantee. Scope deduplication by project/endpoint and make acceptance idempotent.

    Request Hooks

    If a deployment needs an OpenAI-only setting, use a request hook rather than changing portable agent code. Import responses from github.com/openai/openai-go/v3/responses and add this to the driver’s existing construction callback to keep response storage explicitly disabled:

    driver.ResponsesHook(func(params *responses.ResponseNewParams) {
    	params.Store = openai.Bool(false)
    })

    This fragment also imports openai from github.com/openai/openai-go/v3. It reinforces a request setting, not a guarantee against provider retention. The hook sees the translated history, tools, output schema, and defaults before each Responses request, including each agent turn and each streaming request. It does not run during construction. Its edits are not reset by the driver: setting params.Store = openai.Bool(true) deliberately changes the storage request. Do not use store: false as a guarantee against provider retention.

    Keep captures concurrency-safe, do not retain request parameters, and check that the server supports the fields you set. ChatHook provides the corresponding Chat Completions boundary, including an explicit Store override. For a Chat stream, the driver subsequently sets stream_options.include_usage to true, even if the hook disabled it. Neither hook applies to embeddings.

    Native Clients

    If your application already configures the official SDK, import openai from github.com/openai/openai-go/v3 and pass your existing client openai.Client to openaillm.NewFrom(client). The caller retains ownership and connection and retry settings; the driver’s API mode, schemas, batch size, and hooks still apply.

    NewFrom ignores connection edits but validates settings just like New. Construction callbacks run once in order on a fresh draft; retained drafts cannot alter published settings. Native client internals and hook captures remain shared. A nil hook clears it; hooks must not retain request parameters.

    To make SDK calls outside the contract, use the existing driver’s client:

    client := driver.Client()
    models, err := client.Models.List(ctx)

    Here ctx is your request or command context. On success, models.Data contains one page of provider model records. Native operations do not inherit the facade’s validation, events, or telemetry and are not portable contract methods.

    Local Servers

    Local Development shows how to point the driver at a server on your machine.

    API References

    For every function and option, see the openaillm API reference and the optional OpenAI webhook adapter.

    Open a bulk documentation archive?

    This file contains every guide in its scope, not API declarations. Loading it all into an agent can exhaust its context window and increase processing time and cost. For coding tasks, start with the index and read only the relevant pages.

    Use the full archive for offline indexing, bulk processing, or deliberate large-context use.

    Browse the index instead
    Open archive