php-llm/llm-chain
php-llm/llm-chain is a Laravel/PHP package for composing multi-step LLM workflows, chaining prompts and model calls into reusable pipelines with a fluent API. Build reliable AI features with structured, maintainable chains.
This release is a rebrand with a breaking change. php-llm/llm-chain 0.25.0 is now Symfony AI (PR #390, marked chore!). Before anything else, confirm the new Composer package name and namespace root on Packagist/GitHub — the PhpLlm\LlmChain\... namespace is not expected to survive, and every use statement in your app will need updating. If you are mid-project, pin to 0.24.1 until you can do the migration in one pass.
Minimal steps after upgrading:
composer require the new Symfony AI package(s) — the project is a monorepo, so you will likely pull a core package plus a platform bridge (OpenAI, Anthropic, Albert, Ollama, etc.)..env rather than in code.AiServiceProvider, and bind it to an interface so tests can swap in a fake.php artisan tinker before wiring it into a controller.First use case: a single non-streaming Chat call behind a queued job — this exercises the credential path, the HTTP client, and the message/response objects with the least surface area. Add streaming and tools only once that is green.
If you are on Laravel 11+, the HTTP client and Http::fake() integration is the fastest debugging lever you have; make sure your first call goes through Laravel's HTTP client factory so you can assert on requests in tests.
Wrap the platform in a container binding. Do not new up model objects in controllers. Bind a configured chat model (or a small wrapper around it) as a singleton and inject it. This is the difference between a package you can test and one you cannot.
$this->app->singleton(ChatModel::class, fn () => /* platform factory */);
Chain as a pipeline, not a god-object. The package's core abstraction is a chain of inputs → model call → output processors. Keep each processor single-purpose (e.g. "inject RAG context", "inject memory", "format as JSON") so you can reorder and test them independently. Compose chains in a dedicated class per feature rather than assembling them inline.
Memory injection now reaches the system prompt. 0.25.0 adds basic setup for injecting memory into the system prompt (PR #387). This is the right place for durable facts about the user or tenant. Keep the injected block small and deterministic — system prompt memory competes with your instructions for the model's attention, and it is the easiest thing to accidentally make non-cacheable.
Message IDs are now AbstractUid&TimeBasedUidInterface. PR #384 loosens the type so any time-ordered UID works. In practice: stop asserting on a concrete UID class in your tests, and stop assuming UUIDv4. If you persist message IDs to the database, confirm your column type accepts the new format before deploying.
Use tool calling for side effects, not for data fetching. Tools that mutate state (create a ticket, send a mail) should be dispatched as jobs from inside the tool so a retried or hallucinated call is at least idempotent and observable. Read-only tools can run inline.
Queue everything user-facing. Streaming from a queue is awkward; the pragmatic split is: synchronous + streamed for interactive chat UI, queued + non-streamed for summaries, classification, embeddings, and anything triggered by a model event.
Fake at the HTTP layer in tests, and additionally provide a stub chat model for unit tests of your own chain/processor logic. Two layers, two purposes: HTTP fakes catch payload regressions, stub models keep processor tests fast.
Albert API support (PR #366) is a French government LLM gateway — useful if you need EU-hosted inference for compliance. It follows the same platform interface as the others, so your abstraction should already accommodate it; if it does not, your abstraction is too thin.
chore!: PHP LLM becomes Symfony AI is a hard break. Search-and-replace PhpLlm\ across your codebase, config, and any serialized class names in queues (failed_jobs, jobs payloads) — in-flight queued jobs referencing old class names will fail after deploy. Drain the queue before switching.suggests were replaced with runtime checks (PR #385). Missing optional dependencies now surface as exceptions at call time rather than as Composer hints. If you only installed the core package, an optional feature (a platform bridge, a vector store, a tokenizer) will fail at runtime, not at composer install. Install every optional package you actually intend to use, and add a boot-time smoke test.php artisan config:cache in deploy, and remember that credentials resolved inside a package constructor may be read before your config is finalized. Resolve lazily, inside the binding closure.OutputProcessors (post-processing, validation, JSON repair), custom Toolbox/tool definitions (wrap your existing Laravel actions), and a custom platform implementation if you are on an internal gateway — the platform interface is the smallest thing to implement."~0.25.0" with a scheduled review beats "^0.25" for anything in production.How can I help you explore Laravel packages today?