Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Vizra Adk Laravel Package

vizra/vizra-adk

Vizra ADK is a retired AI agent development kit for Laravel; v0.0.48 is the final release (PHP 8.2+, Laravel 11-13). It still installs and works when pinned, but gets no fixes or security patches. For new work use the official Laravel AI SDK, or vizra/evals for testing agents.

View on GitHub
Deep Wiki
Context7

Getting Started

Read the banner first. Vizra ADK is retired as of August 2026; v0.0.48 is the final release. It still installs and runs (PHP 8.2+, Laravel 11/12/13), Packagist keeps serving it, and the MIT license means forking is a legitimate option — but there will be no bug fixes, no security patches, and no issues triaged. Treat adoption as "vendoring a stable, frozen dependency," not "adding a maintained package."

Pin exactly, never float:

"vizra/vizra-adk": "0.0.48"

Commit composer.lock. The hard constraint to be aware of is prism-php/prism pinned at ^0.99.20 — that transitive dependency will age out of compatibility with future Laravel releases long before anything else does.

Install and scaffold:

composer require vizra/vizra-adk:0.0.48
php artisan vizra:install        # publishes config + migrations
php artisan migrate
php artisan vizra:make:agent CustomerSupportAgent
php artisan vizra:chat customer_support

Set at least one provider key (OpenAI / Anthropic / Google) in .env — Prism reads them from the standard env vars. The package requires MySQL or PostgreSQL; SQLite is not a supported target.

Where to look first, in order:

  1. The published config file — this is where provider/model defaults, persistence, MCP servers, and dashboard settings live. Read it end-to-end before writing an agent; most "why is it doing that?" questions resolve here.
  2. docs/MACROS.md in the repo — the macro pattern is the sanctioned extension point and the cleanest way to add cross-cutting behaviour without touching vendor code.
  3. skills/vizra-agent-creation/SKILL.md — effectively a canonical agent template. If you're having an AI assistant generate agents, point it at this file rather than letting it guess property names.
  4. changelog/0.0.*.md — the only reliable record of behavioural fixes (streaming hooks, message persistence, MCP SSE parsing, attachment serialization). The hosted docs at vizra.ai/docs will drift and eventually disappear.
  5. The Livewire dashboard — fastest way to sanity-check an agent's behaviour before wiring it into application code.

First use case: a read-only internal agent with exactly one tool. Do not start with a multi-agent workflow. Build a support/lookup agent that queries one Eloquent model, drive it from php artisan vizra:chat, and only then expose it over HTTP. This isolates the two failure modes you'll actually hit first — tool schema errors and context/persistence weirdness — from your application code.

class CustomerSupportAgent extends BaseLlmAgent
{
    protected string $name = 'customer_support';
    protected string $description = 'Helps customers with inquiries';
    protected string $instructions = 'You are a helpful customer support assistant.';
    protected string $model = 'gpt-4o';
    protected array $tools = [OrderLookupTool::class];
}

$response = CustomerSupportAgent::run('I need help with my order')
    ->forUser($user)
    ->go();

No registration step is required — agents are auto-discovered.


Implementation Patterns

Wrap the package behind your own boundary

Given the retirement status, the highest-value day-to-day pattern is not a Vizra pattern at all: put every agent invocation behind a thin application-level service or interface.

interface SupportAssistant
{
    public function reply(User $user, string $message): string;
}

class VizraSupportAssistant implements SupportAssistant
{
    public function reply(User $user, string $message): string
    {
        return CustomerSupportAgent::run($message)
            ->forUser($user)
            ->go();
    }
}

When (not if) you migrate to the official Laravel AI SDK, or a community fork, you swap one binding. Controllers, jobs, and Livewire components never see Vizra\VizraADK\*.

Agent definition conventions

Keep three things disciplined in every agent class:

  • $name is a contract. It's the handle used by vizra:chat, by Agent::run('customer_support'), and by the registry. Renaming it silently breaks callers and any stored session rows keyed to it.
  • $instructions in a heredoc. Long system prompts inline as single-quoted strings become unreadable. Use nowdoc (<<<'INSTRUCTIONS') so $ in the prompt isn't interpolated.
  • $tools is a whitelist. Every tool listed is a capability the model may invoke unsupervised. Keep it minimal per agent and prefer several narrow agents over one omnibus agent.

Set $temperature and $maxTokens explicitly rather than leaving them null. Null means "provider default," which differs across OpenAI/Anthropic/Gemini and drifts between model versions — the same agent will behave differently after a silent upstream change.

Tools

ToolInterface is a two-method contract: definition() returns an OpenAI-style JSON schema, execute(array $arguments, AgentContext $context) returns a string.

public function execute(array $arguments, AgentContext $context): string
{
    $order = Order::where('user_id', $context->userId())
        ->findOrFail($arguments['order_id']);

    return json_encode(['status' => 'success', 'order' => $order->toArray()]);
}

Two rules that matter in practice:

  • Always return JSON. The model reads the string as an observation. Returning "Found order 123" works but gives the model nothing structured to reason over; returning json_encode([...]) with an explicit status key lets it distinguish success from failure without inferring from prose.
  • Scope inside the tool, not in the prompt. The AgentContext carries the user/tenant. Filter the query by it. Never rely on the instructions saying "only look up this user's orders" — prompt-level authorization is not authorization.

Multi-tenancy

Two mechanisms, and they solve different problems:

  • Dynamic agent name resolution — register the same class under runtime names (team_agent_24) so per-tenant sessions, traces, and state stay isolated. Resolution is three-tier: explicit context → registry → fallback.
  • withUserContext(array) — pass minimal user data instead of serialising a full User model. Use this. Serialising models into agent state is where attachment/serialization bugs bite, and it puts columns you didn't intend into an LLM prompt.

For MCP servers, per-tenant config overrides are deep-merged over the base config via AgentContext — the intended use case is passing tenant-specific API keys so one shared agent definition serves many tenants.

Execution modes

The package offers conversational, scheduled, webhook, event-driven, and queued triggering. In application code, standardise on queued execution for anything user-triggered that isn't a synchronous chat reply:

class RunSupportAgent implements ShouldQueue
{
    public function __construct(
        public int $userId,
        public string $message,
    ) {}

    public function handle(VizraSupportAssistant $assistant): void
    {
        $assistant->reply(User::findOrFail($this->userId), $this->message);
    }
}

LLM calls take seconds and cost money; they belong on a queue with an explicit $timeout and $tries = 1 (retrying a tool-using agent can double side effects).

Streaming to the UI

For interactive surfaces, stream rather than await. Prism's streaming events are surfaced as typed StreamEvent objects, and the package's own Livewire chat interface demonstrates the wire:stream pattern. The practical integration tip: stream tokens into a temporary buffer, and persist the final assembled message only on completion — otherwise a dropped connection leaves a half-written row in your message table.

Evaluation and tracing

The built-in evaluation framework (LLM-as-a-judge, CSV-driven) is the right place to encode "this agent still behaves correctly" before you touch instructions or swap models. Run it in CI against a fixed set of prompts. Two caveats: judge output is itself non-deterministic, so assert on aggregate pass rates rather than exact strings; and every CI run costs tokens — gate it to changes touching app/Agents/** or tool classes.

For debugging individual runs, lean on the traces before adding Log::debug calls. The trace records each step, tool call, and token count, which is almost always enough to answer "why did it call that tool twice."


Gotchas and Tips

The retirement is the biggest gotcha. Mitigations, in priority order: pin 0.0.48 exactly; commit the lockfile; wrap all usage behind your own interface (above); subscribe to nothing and instead set a calendar reminder to re-evaluate the dependency graph every six months. If you hit a genuine bug, forking is explicitly welcomed — and may be cheaper than working around it. Do not expect upstream fixes to security issues in the package's HTTP/MCP surface.

Livewire version friction. The README declares Livewire ^4.0 as a requirement for the dashboard, while the changelog references Livewire 3's wire:stream. If your application is on Livewire 3, the dashboard is likely to conflict with your app's Livewire constraint. Options: run the dashboard in a separate service/container, or skip the dashboard entirely (agents, tools, and the Artisan chat command don't need it). Never let the dashboard's UI dependency dictate your app's Livewire version.

Auto-discovery plus config caching. Agents are discovered from the filesystem, which interacts badly with cached configs and stale autoloaders. After creating or renaming an agent class:

composer dump-autoload
php artisan optimize:clear

A renamed agent that "doesn't exist" is almost always a cache, not a code, problem.

maxSteps defaults to 5. That's the ceiling on tool-execution loops. Agents that chain lookups will silently stop mid-task; bump it deliberately per agent. Conversely, raising it without a plan invites runaway loops and surprising bills — pair any increase above ~8 with a hard timeout and a per-run token budget.

Message persistence is incremental — don't fight it. From 0.0.24, StateManager inserts only new messages rather than delete-and-reinsert. Consequences: don't manually rewrite rows in the message table (you'll desync the in-memory state), and be aware that very long conversations grow into genuinely long histories that inflate every request's token cost. Cap history length at the application level if you support long-lived threads.

Run the migrations, including the ones that look cosmetic. 0.0.35 widened agent_messages.content to hold large MCP responses; skipping it produces truncated or failed inserts the moment a tool returns a big payload. 0.0.38 fixed Image/Document serialization — if you pass attachments, don't pin below it.

MCPClientManager is deliberately non-singleton. It's bound non-singleton for tenant isolation. Two implications: resolving it per execution is correct and cheap, and you must not capture it in a singleton, a static, or a constructor-injected property on a long-lived object. Queue workers are long-lived processes — a captured manager will leak one tenant's config/key into another tenant's job. Resolve it inside handle().

Streaming skips hooks if you're on an older patch. afterLlmResponse was not called for streaming responses until 0.0.35. If you use that hook for logging, cost accounting, or persistence, this is exactly the kind of silent gap that produces "our token dashboard undercounts by 30%." Verify against a streamed run, not just a blocking one.

Tool definition() schema errors surface as model confusion, not exceptions. A malformed parameters block or a missing required entry doesn't throw — the model just ignores the tool or calls it with garbage. Symptom: an agent that "decides not to look anything up." Test each tool by asserting the model calls it with valid arguments for a canonical prompt, not by calling execute() directly.

execute() has no authorization layer. Policies, Gate, and middleware do not run inside tools. Every tool body that touches tenant data must re-derive scope from AgentContext. Treat every $arguments value as attacker-influenced — the model is generating them, and prompt injection from tool output is a real path.

Debugging order of operations. When an agent misbehaves: (1) open the trace for the run, (2) check whether the right tool was called with the right arguments, (3) only then consider the instructions. Nine times out of ten the model did what the tool schema told it to do, and the fix is in definition(), not the system prompt.

Use the macro system instead of subclassing internals. AgentBuilder::macro('track', ...) and friends are the supported extension point for cross-cutting concerns — token accounting per billable model, tagging runs for analytics, injecting tenant IDs. Subclassing BaseLlmAgent internals or copying vendor classes into app/ creates an unmergeable fork; macros survive upstream patch bumps and are far easier to delete when you migrate.

Keep the migration plan written down. The ADK builds agents; vizra/evals tests them; the official Laravel AI SDK builds them going forward. There is no drop-in path, so the realistic plan is: keep your agents as plain classes with minimal logic, keep all orchestration in your own jobs/services, and keep prompts in your own config or database — so the eventual move is re-wiring, not rewriting.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
dbravoan/dbcms-admin-theme-dbcms
dbravoan/dbcms-theme-db-cyber
dbravoan/dbcms-blocks-basic
dbravoan/dbcms-module-content-audit
dbravoan/dbcms-module-ai-assistant
paygate-to/paygate-php
padosoft/askmydocs-connector-base
defstudio/supercharged-carbon
bernskioldmedia/laravel-matomo
bernskioldmedia/laravel-harvest
bernskiold/laravel-data-scrubber
bernskiold/laravel-currency-converter
bernskiold/laravel-campaign-monitor
bernskioldmedia/laravel-autotranslate
siberfx/telegram-bot
roy-sandip/laravel-view-extended
gregsphotography/site-builder-core
roy-sandip/laravel-view
anodyne/blade-fluentui-icons
webx-ui/module-catalog-collections