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_KEY is required before outbound API calls.
  • OPENAI_API_BASE_URL defaults to the OpenAI REST API base URL.
  • OPENAI_RESPONSES_MODEL is the default model for /responses.
  • OPENAI_EMBEDDINGS_MODEL is the default model for /embeddings.
  • OPENAI_REALTIME_MODEL is the default model for Realtime client secrets.
  • OPENAI_STORE_RESPONSES=false makes 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(): bool
  • createResponse(string|array $input, array $options = []): array
  • createTextResponse(string|array $input, array $options = []): string
  • createEmbeddingResponse(string|array $input, array $options = []): array
  • embedText(string $input, array $options = []): array
  • embedTexts(array $inputs, array $options = []): array
  • createRealtimeClientSecret(array $session = [], array $options = []): array
  • extractOutputText(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_id or document_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_KEY in .env, never in Git.
  • Keep provider-specific domain mapping in project services, not controllers.
  • Use deterministic project IDs in metadata instead of logging content.
  • For semantic search, store vectors in project-owned tables or a search adapter; OpenAIClient does 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.