Ollama PHP Client

Local models through explicit generation and embedding contracts.

A PHP client for Ollama with separate text and embedding DTOs, replaceable transport and explicit response-handling boundaries.

PHP · Ollama · Embeddings · LLM

Role

Library author: PHP client architecture, DTOs, HTTP transport and tests.

Open source · September 2026. This case examines public commit 2eed0e18. Source links and implementation limits refer to that revision.

Calling a local model from PHP

Ollama runs a model in a separate process and exposes an HTTP API. I wrote a PHP client that expresses two specific operations, text generation and embeddings, as application requests and responses. It does not store conversations or choose a model for the user. Its responsibility is communicating with the server and mapping its response.

I am the package author, responsible for its architecture, DTOs, transport and tests. The Composer manifest specifies PHP ^7.2|^8.0, cURL and JSON. The client fits existing PHP code as a Composer dependency.

The application–model boundary
  1. PHP applicationText, model choice and application workflow
  2. Ollama PHP ClientDTOs, HTTP and response mapping
  3. OllamaLoaded model and inference

The application receives text or a vector. Storage, a search index and quality evaluation stay outside the client.

Workflow: prepare text for retrieval

Consider an index of engineering notes. Before writing to a vector database, the application needs an embedding for each note. It passes one text and a model name to EmbeddingRequest, calls embed and receives an EmbeddingResponse. The client does not dictate a database: another application adapter can consume the resulting array.

The example expects a local Ollama server on port 11434 with nomic-embed-text already downloaded. It prints the actual response dimension instead of assuming one. The text is synthetic; this call does not establish retrieval quality on a real document collection.

<?php
require __DIR__ . '/vendor/autoload.php';

use Tenqz\Ollama\Embedding\Application\DTO\Request\EmbeddingRequest;
use Tenqz\Ollama\Embedding\Infrastructure\Client\OllamaEmbeddingClient;
use Tenqz\Ollama\Transport\Infrastructure\Http\Client\CurlTransportClient;

$http = new CurlTransportClient('http://127.0.0.1:11434', [], 120, 10);
$client = new OllamaEmbeddingClient($http);
$request = new EmbeddingRequest(
    'nomic-embed-text', 'How to configure a reverse proxy'
);
$request->setKeepAlive('5m');

$response = $client->embed($request);
$vector = $response->getEmbedding();
echo 'Dimension: ' . $response->getDimension() . PHP_EOL;

Store the result with its document identifier and the chosen model. When changing models, equal array lengths do not make old and new embeddings interchangeable. The client provides integration data; the application decides how to rebuild its index.

Decision 1: separate generation from embeddings

The operations have different contracts. GenerationRequest describes the prompt, system message and generation options; EmbeddingRequest describes a model and input text. Separate clients and exceptions let callers distinguish a generation failure from a failed search-vector preparation. These are narrow boundaries around concrete operations rather than one generic object with many unrelated optional fields.

Decision 2: expose an explicit response object

EmbeddingResponse provides getEmbedding, getDimension and timing metadata when the server supplies it. The first method rejects an empty embedding list. The client checks that an embeddings array exists, but it does not establish vector quality. Numerical validation, expected dimensions and relevance evaluation still belong to the integration.

Decision 3: keep transport replaceable

Both clients depend on TransportClientInterface. The cURL implementation has separate connection and total request timeouts, set to 10 and 120 seconds in this example. Tests substitute the transport to inspect endpoints, payloads and response mapping without downloading a model. A real Ollama server remains a separate integration check.

An inspectable result

The repository includes the /api/embed implementation and client tests covering the endpoint, request body, result mapping and errors. Response DTO tests separately examine empty data, dimensions and metadata preservation.

The result is a Composer library with distinct generation and embedding contracts. Unit tests use a mocked transport to check HTTP response mapping into DTOs and exceptions. Model quality and inference duration are evaluated separately on the chosen server and input set.

Version limits

The embedding request accepts a string input. An array of vectors in the response DTO does not mean the request supports batching several texts. For generation, use setStream(false), also the default. The stream field is serialized, but the transport buffers the response and the JSON adapter parses the whole body. This implementation does not provide incremental NDJSON token delivery.

The library also does not download models, manage a GPU or provide a job queue. Long requests occupy a PHP process; background execution and protection of a remote Ollama endpoint belong to the application architecture.

Try it

Start Ollama locally, download the embedding model and install the package. Save the PHP example beside vendor and run it from the terminal. Allow for model loading on the first request.

composer require tenqz/ollama
ollama pull nomic-embed-text

The README describes other request parameters; tenqz/ollama contains the current source. Start with one short text and inspect its vector dimension before connecting a persistent index.

GitHub

Let's make the complex understandable.

Architecture, engineering leadership, and AI in development — when the system is too important to simplify, and too expensive not to own.

LinkedIn Telegram Email