vielhuber / aihelper
Consistent interface for multiple ai providers.
Requires
- php: >=8.1
- fakerphp/faker: ^1.24.1
- vielhuber/stringhelper: ^7.3.4
Requires (Dev)
- phpunit/phpunit: ^13
- vlucas/phpdotenv: ^5.6.4
This package is auto-updated.
Last update: 2026-08-04 05:29:16 UTC
README
🤖 aihelper 🤖
aihelper provides a single, consistent php interface for multiple ai providers. it supports chat and vision use cases, session-aware conversations, robust retry logic, logging, simple cost tracking, and optional model context protocol (mcp) integration — all behind one method.
installation
composer require vielhuber/aihelper
usage
use vielhuber\aihelper\aihelper; $ai = aihelper::create( provider: 'anthropic', // anthropic|google|openai|xai|deepseek|openrouter|cliproxyapi|elevenlabs|nvidia|llamacpp|lmstudio|claudecode|codex|opencode model: 'claude-opus-4-1', // claude-opus-4-1|gemini-2.5-pro|gpt-5|grok-4|deepseek-chat|qwen/qwen3-coder-next|... effort: null, // null|none|minimal|low|medium|high|xhigh|max — reasoning effort, ignored when the provider/model has no supported reasoning control temperature: 1.0, // controls the randomness of the text generated api_key: '**API Key**', log: 'output.log', max_tries: 1, timeout: 300, // maximum timeout (increase for long tasks) mcp_servers: [ [ 'name' => 'example-mcp', 'url' => 'https://modelcontextprotocol.io/mcp', 'authorization_token' => '...', 'allowed_tools' => ['tool_name_1', 'tool_name_2'], // optional: restrict to specific tools (null or omit = all tools allowed) 'default_tool_arguments' => ['tool_name_1' => ['optional_argument' => 'default']], 'forced_tool_arguments' => ['tool_name_1' => ['fixed_argument' => 'value']] ] ], mcp_servers_call_type: 'remote', // remote = provider calls mcp servers directly, local = client-side tool loop via aihelper session_id: null, // submit session to continue a conversation (get with $ai->getSessionId()) history: null, // submit messages (get with $ai->getSessionContent()), system_prompt: null, // works with every provider — cli harnesses receive it as a real system prompt, everyone else gets it prepended to the session (same as writing it into `history` yourself or calling $ai->setSystemPrompt() later) stream: false, url: null, // overwrite connection url (e.g. for llamacpp/lmstudio) enable_thinking: null, // true|false|null — force reasoning/thinking on/off; null = provider default (see below) auto_compact: false // true = transparently compact the session when it approaches the model's context window ); $ai->ask(prompt: 'Wer wurde 2018 Fußball-Weltmeister?'); // ['response' => 'Frankreich.', 'success' => true, 'costs' => 0.001] $ai->ask(prompt: 'Was ist auf dem Bild zu sehen?', files: 'lorem.jpg'); // ['response' => 'Auf dem Bild ist eine Katze zu sehen.', 'success' => true, 'costs' => 0.001] $ai->ask(prompt: 'Was wird in der Audiodatei gesagt?', files: 'lorem.mp3'); // ['response' => 'In der Aufnahme wird gesagt: "Hallo, wie geht es dir?"', 'success' => true, 'costs' => 0.001] $ai->ask(prompt: 'Wie lautet das erste Wort in der PDF?', files: 'lorem.pdf'); // ['response' => 'Das erste Wort lautet "Lorem".', 'success' => true, 'costs' => 0.001] $ai->ask(prompt: 'Fasse die folgenden Dokumente zusammen.', files: ['1.pdf', '2.jpg']); // ['response' => '...', 'success' => true, 'costs' => 0.001] $ai->ask(prompt: 'Was habe ich vorher gefragt?'); // ['response' => 'Du hast gefragt: "Wie lautet das erste Wort in der PDF?"', 'success' => true, 'costs' => 0.001] $ai = aihelper::create(provider: 'openai', model: 'gpt-image-1', api_key: '**API Key**'); $ai->image( prompt: 'a red cat on a blue couch', // text description of the desired image size: '1024x1024', // e.g. '512x512'|'1024x1024'|'1792x1024' n: 1, // number of images to generate input_file: null, // path|url|base64 — switches to edit/variation mode output_file: null // path — when set, file is written and the path is returned instead base64 ); // ['response' => 'iVBORw0KGgo...', 'success' => true, 'costs' => 0.04] $ai->image(prompt: 'a red cat on a blue couch', output_file: '/tmp/cat.png'); // ['response' => '/tmp/cat.png', 'success' => true, 'costs' => 0.04] $ai->image(prompt: 'a red cat on a blue couch', n: 3, output_file: '/tmp/cat.png'); // ['response' => ['/tmp/cat-1.png', '/tmp/cat-2.png', '/tmp/cat-3.png'], 'success' => true, 'costs' => 0.12] $ai->image(prompt: 'add a hat', input_file: 'cat.png'); // edit / variation $ai = aihelper::create(provider: 'openai', model: 'gpt-4o-mini-tts', api_key: '**API Key**'); $ai->audio( prompt: 'Hallo, wie geht es dir?', // text to synthesize voice: 'alloy', // provider voice id (e.g. 'alloy'|'echo'|'nova') speed: null, // optional, e.g 1.2 output_file: null // path — when set, file is written and the path is returned instead base64 ); // ['response' => 'SUQzBAA...', 'success' => true, 'costs' => 0.001] $ai->audio(prompt: 'Hallo, wie geht es dir?', output_file: '/tmp/hi.mp3'); // ['response' => '/tmp/hi.mp3', 'success' => true, 'costs' => 0.001] aihelper::getProviders() // gets overview of providers and models with costs and additional infos aihelper::create(provider: '...', api_key: '...')->fetchModels() // get resolved model catalog $ai->ping() // gets health, returns true|false $ai->getSessionId() // get current session id $ai->getSessionContent() // gets messages in chat history // manually populate history $ai = aihelper::create(...); $ai->prependPromptToSession(prompt: '...', files: [...]); $ai->appendPromptToSession(prompt: '...', files: [...]); aihelper::getMcpOnlineStatus( url: 'https://modelcontextprotocol.io/mcp', authorization_token: '...' ); // true|false aihelper::getMcpMetaInfo( url: 'https://modelcontextprotocol.io/mcp', authorization_token: '...' ); // ['name' => '...', 'online' => true, 'instructions' => '...', 'tools' => ['...']] aihelper::callMcpTool( name: 'foo-123', args: ['foo' => 'bar'], url: 'https://modelcontextprotocol.io/mcp', authorization_token: '...' ); // ['jsonrpc' => '2.0', 'id' => 123, 'result' => ['content' => [['type' => 'text', 'text' => '...']]]]
cli harnesses
the providers claudecode, codex and opencode drive the locally installed cli agent instead of a chat completion endpoint. they own their system prompt, tools and history, so only the newest user turn is handed over. install and log them in once:
npm i -g @anthropic-ai/claude-code @openai/codex
curl -fsSL https://opencode.ai/install | bash
claude auth login
codex login --device-auth
opencode auth login
every turn continues the newest thread of cli_workdir and opens a new one only when that directory has none yet.
set cli_ssh_host to run the agent on another machine instead of locally — the working directory, the login and the threads are then that machine's:
$ai = aihelper::create( provider: 'claudecode', system_prompt: 'Du bist ein Assistent für Tabellen und Tickets.', cli_workdir: '/var/www/project', // optional, defaults to a throwaway directory per session cli_ssh_host: 'host.docker.internal', // optional, runs locally when omitted cli_ssh_user: 'root', // optional cli_ssh_port: 22, // optional cli_ssh_key: '/root/.ssh/id_ed25519', // optional cli_skills: [ 'excel' => "---\nname: excel\ndescription: create and read xlsx files. use for tables and grades.\n---\n\nuse the excel_create_file tool ...", 'jira' => "---\nname: jira\ndescription: read jira issues via jql. use for tickets, sprints, backlogs.\n---\n\n..." ] ); // get cli usage limits for claude code, codex, opencode and antigravity // to get exact opencode go usage limits, sign in at `https://opencode.ai`, // set OPENCODE_GO_AUTH_COOKIE in .env to the value of cookie "auth" $ai->getCliUsageLimits() // [ // ['type' => '5-hour', 'scope' => null, 'percent used' => 20, 'resets_at' => '2026-06-29T17:59:00+02:00'], // ['type' => 'weekly', 'scope' => null, 'percent used' => 10, 'resets_at' => '2026-07-06T03:03:00+02:00'], // ['type' => 'weekly', 'scope' => 'Fable', 'percent used' => 93, 'resets_at' => '2026-07-06T03:03:00+02:00'] // ] // get manually redeemable codex reset credits $ai->getCliUsageResetCredits() // ['available_count' => 1, 'credits' => [['title' => 'Full reset', 'expires_at' => '...']]] // redeem the next available codex reset credit $ai->triggerCliUsageReset() // ['success' => true, 'status' => 'reset', 'windows_reset' => 2] // get all requests from a local clis aihelper::getCliApiRequests( limit: 100, // null: all date_from: '2026-07-01 00:00:00', // null: all date_until: '2026-07-31 23:59:59', // null: all include_body: false, group_by: false // true = collapse per project ); // delete request logs only aihelper::purgeCliApiRequestLogs( date_from: null, // null: all date_until: null // null: all );
streaming
aihelper can stream model output to a browser using server‑sent events (see). in this mode the php backend connects to the model provider with http streaming and forwards chunks to the client as sse events in real time. see an example implementation at /tests/stream/index.html.
$ai = aihelper::create( /* ... */ stream: true /* ... */ ); $result = $ai->ask('Wer wurde 2018 Fußball-Weltmeister?'); /* ... */ // echoes stream /* ... */ // $result = ['response' => 'Frankreich.', 'success' => true, 'costs' => 0.001]
if streaming stutters on apache2 with php‑fpm, be sure that gzip is disabled for the streaming route and also adjust your virtualhost so fastcgi forwards packets immediately (no buffering):
before
<VirtualHost ...>
...
<FilesMatch \.php$>
SetHandler "proxy:unix:/var/run/php/php8.5-fpm.sock|fcgi://localhost/"
</FilesMatch>
...
</VirtualHost>
after
<VirtualHost ...> ... <Proxy "fcgi://localhost-stream/" enablereuse=on flushpackets=on> </Proxy> <FilesMatch \.php$> <If "%{HTTP:Accept} -strmatch '*text/event-stream*'"> SetHandler "proxy:unix:/var/run/php/php8.5-fpm.sock|fcgi://localhost-stream/" SetEnv no-gzip 1 RequestHeader unset Accept-Encoding </If> <Else> SetHandler "proxy:unix:/var/run/php/php8.5-fpm.sock|fcgi://localhost/" </Else> </FilesMatch> ... </VirtualHost>