OpenAIClient - viames/pair GitHub Wiki
Pair framework: OpenAIClient
Pair\Services\OpenAIClient is a lightweight HTTP client for OpenAI APIs.
It focuses on the integration paths Pair applications need most often:
- Responses API for text, tool-capable, and multimodal model responses
- Embeddings for semantic search and document indexing
- Realtime client secrets for browser-facing voice or low-latency modules
The Pair core does not require an OpenAI SDK. The client uses cURL and keeps the provider dependency optional.
Configuration
OPENAI_API_KEY=
OPENAI_API_BASE_URL="https://api.openai.com/v1"
OPENAI_RESPONSES_MODEL=gpt-5.4-mini
OPENAI_EMBEDDINGS_MODEL=text-embedding-3-small
OPENAI_REALTIME_MODEL=gpt-realtime
OPENAI_TIMEOUT=30
OPENAI_CONNECT_TIMEOUT=5
OPENAI_STORE_RESPONSES=false
Keys:
OPENAI_API_KEYis required before outbound API calls.OPENAI_API_BASE_URLdefaults to the OpenAI REST API base URL.OPENAI_RESPONSES_MODELis the default model for/responses.OPENAI_EMBEDDINGS_MODELis the default model for/embeddings.OPENAI_REALTIME_MODELis the default model for Realtime client secrets.OPENAI_STORE_RESPONSES=falsemakes Responses API calls privacy-first by default; override per request only when later retrieval is required.
Extension path
Pair v4 integrations should be registered explicitly. An application or optional pair/openai package can expose the client as the AI adapter:
use Pair\Core\AdapterKeys;
use Pair\Core\Application;
use Pair\Services\OpenAIClient;
$app = Application::getInstance();
$app->setAdapter(AdapterKeys::AI, new OpenAIClient());
$ai = $app->adapter(AdapterKeys::AI, OpenAIClient::class);
This keeps OpenAI optional and avoids automatic package discovery.
Constructor
__construct(?string $apiKey = null, ?string $apiBaseUrl = null, ?string $responsesModel = null, ?string $embeddingsModel = null, ?string $realtimeModel = null, ?int $timeout = null, ?int $connectTimeout = null, ?bool $storeResponses = null)
Creates the client with explicit values or Env defaults.
Main methods
apiKeySet(): boolcreateResponse(string|array $input, array $options = []): arraycreateTextResponse(string|array $input, array $options = []): stringcreateEmbeddingResponse(string|array $input, array $options = []): arrayembedText(string $input, array $options = []): arrayembedTexts(array $inputs, array $options = []): arraycreateRealtimeClientSecret(array $session = [], array $options = []): arrayextractOutputText(array $response): string
Responses API
Use createResponse() when the caller needs the full OpenAI response.
use Pair\Http\JsonResponse;
use Pair\Services\OpenAIClient;
$client = new OpenAIClient();
$response = $client->createResponse('Summarize this support ticket.', [
'instructions' => 'Return concise Italian text.',
'metadata' => ['ticket_id' => 'ticket_123'],
'max_output_tokens' => 300,
]);
return new JsonResponse([
'id' => $response['id'] ?? null,
'text' => OpenAIClient::extractOutputText($response),
]);
Use createTextResponse() for the common text-only case:
use Pair\Services\OpenAIClient;
$client = new OpenAIClient();
$summary = $client->createTextResponse('Summarize this CRM note.', [
'instructions' => 'Return one sentence.',
'store' => false,
]);
By default Pair sends store=false. Set store=true only when the application explicitly needs later response retrieval.
Embeddings
Use embedText() for one document chunk:
use Pair\Services\OpenAIClient;
$client = new OpenAIClient();
$vector = $client->embedText('Document chunk to index.', [
'dimensions' => 256,
]);
Use embedTexts() for batch indexing:
use Pair\Services\OpenAIClient;
$client = new OpenAIClient();
$vectors = $client->embedTexts([
'First document chunk.',
'Second document chunk.',
]);
embedTexts() returns vectors sorted by the API result index, so the result order matches the input order.
Realtime Client Secrets
Use createRealtimeClientSecret() from a server-side controller when a browser or mobile client needs a short-lived Realtime credential.
use Pair\Http\JsonResponse;
use Pair\Services\OpenAIClient;
$client = new OpenAIClient();
$secret = $client->createRealtimeClientSecret([
'type' => 'realtime',
'audio' => [
'output' => ['voice' => 'marin'],
],
]);
return new JsonResponse([
'client_secret' => $secret['value'] ?? null,
'expires_at' => $secret['expires_at'] ?? null,
]);
Do not expose OPENAI_API_KEY to browsers. Expose only the short-lived client secret returned by OpenAI.
Safe Logging
Do not log raw prompts, uploaded files, model outputs, tool outputs, embeddings, or Realtime transcripts by default.
Prefer logging operational metadata only:
- endpoint or feature name
- selected model
- request duration
- token usage from the API response
- project-side entity IDs such as
ticket_idordocument_id - error code and sanitized error message
If a project requires prompt logging for debugging, make it opt-in, time-limited, access-controlled, and disabled in production by default.
Operational Notes
- Keep
OPENAI_API_KEYin.env, never in Git. - Keep provider-specific domain mapping in project services, not controllers.
- Use deterministic project IDs in
metadatainstead of logging content. - For semantic search, store vectors in project-owned tables or a search adapter;
OpenAIClientdoes not persist embeddings. - For Realtime, create client secrets server-side and send only the returned short-lived value to the frontend.
OpenAI References
See also: API, Integrations, AdapterRegistry, JsonResponse.