citomni/helloai is a reusable CitOmni provider package for conversational AI integrations. It offers one compact, stable internal contract for chat-style requests and responses, so application code can work against a consistent structure rather than provider-specific JSON payloads. The package is intentionally narrow in scope: It is designed to be deterministic, explicit, and operationally useful, not a speculative "universal AI platform."
At its core, HelloAi separates application intent from provider transport. The application submits a normalized request, HelloAi resolves a profile, delegates provider translation to an adapter, performs the outbound HTTP call through the existing CitOmni cURL service, parses the provider response back into a common format, and optionally serves or stores the result through a database-backed cache. This design keeps the public surface area small while preserving room for provider-specific capabilities through explicit escape hatches.
- Unified chat contract for CitOmni applications with one compact internal request/response format across providers.
- Profile-driven provider selection so applications target stable profile ids rather than provider-specific payload logic.
- Adapter-based translation layer that keeps provider-specific JSON, headers, and response parsing out of application code.
- Deterministic database-backed caching keyed from the normalized request payload.
- Operationally explicit logging and sanitization with secret masking and useful execution context.
- Reuse of the existing CitOmni cURL service instead of introducing a parallel transport abstraction.
- CLI integration out of the box through
helloai:chatfor diagnostics, development, and scripting.
Most chatbot APIs differ in naming, request shape, option semantics, authentication headers, and response envelopes. Those differences are operationally real, but they should not leak into every controller, command, or application service. HelloAi exists to absorb that variability behind a single internal request/response model and a profile-based configuration layer. Profiles select concrete adapters and models; adapters perform provider-specific translation; the application stays focused on intent.
In practical terms, this yields four advantages:
- A single internal request format across providers.
- A single normalized response format for downstream application code.
- Explicit profile-driven configuration rather than implicit provider branching.
- Deterministic caching and logging without inventing a parallel transport stack.
HelloAi follows a deliberately conservative architecture:
- Profiles, not providers, are selected by the application. A profile defines the adapter, model, endpoint, API credentials, and timeout policy.
- Adapters own translation. They build provider requests, headers, and response normalization.
- The existing CitOmni cURL service remains the transport. HelloAi does not introduce its own generic transport layer.
- Validation is structural, not doctrinaire. The package validates request shape and required fields, while leaving provider-specific extensibility available through explicit options.
- Caching is deterministic. Identical normalized requests map to identical cache keys.
- Logging is explicit and sanitized. Secrets are masked, but operationally relevant context remains available for debugging and observability.
- PHP 8.2+
ext-jsoncitomni/kernelcitomni/infrastructure- A CitOmni application with the existing cURL service available
- MySQL or MariaDB for the supplied cache table if response caching is enabled
OPcache is strongly recommended in production.
composer require citomni/helloai
composer dump-autoload -oRegister the package provider in config/providers.php:
<?php
declare(strict_types=1);
return [
\CitOmni\HelloAi\Boot\Registry::class,
];If response caching is enabled, apply the package cache table to your application database.
Once registered, the package exposes the helloAi service and the helloai:chat CLI command.
The primary entry point is the HelloAi service:
$this->app->helloAi->chat(array $request): arrayThe service is responsible for:
- Normalizing the internal request.
- Resolving the selected profile.
- Validating structural request shape.
- Building a deterministic cache key.
- Reading from cache before any external API call.
- Instantiating the configured adapter.
- Building the provider payload and headers.
- Sending the HTTP request via
$this->app->curl->execute(...). - Parsing and normalizing the provider response.
- Writing successful responses back to cache.
- Returning the common response format.
HelloAi uses a compact internal request model centered on profile, messages, options, tools, provider_options, and debug.
[
'profile' => 'claude-sonnet-4-6',
'messages' => [
[
'role' => 'system',
'content' => [
['type' => 'text', 'text' => 'You are a precise assistant.'],
],
],
[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => 'Write a short summary of this product.'],
],
],
],
'options' => [
'temperature' => 0.7,
'max_output_tokens' => 400,
],
'tools' => [],
'provider_options' => [],
'debug' => [
'include_raw_response' => false,
'include_built_request' => false,
],
]The internal role vocabulary is:
systemdeveloperuserassistanttool
Messages use content blocks rather than a single message string. This keeps the internal contract structurally consistent and allows adapters to map content into provider-native payloads. Text blocks are the canonical baseline:
[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => 'Hello'],
],
]HelloAi does not impose a central whitelist over generic request options. The options array is intentionally open-ended. Adapters may consume the keys they support and ignore or reinterpret the rest according to explicit adapter logic.
Provider-specific deviations belong in provider_options, which acts as the sanctioned escape hatch for capabilities that do not fit the cross-provider abstraction cleanly.
Adapters normalize provider responses into a common structure:
[
'profile' => 'claude-sonnet-4-6',
'provider' => 'anthropic',
'model' => 'claude-sonnet-4-6',
'message' => [
'role' => 'assistant',
'content' => [
['type' => 'text', 'text' => 'Here is your summary...'],
],
],
'finish_reason' => 'end_turn',
'usage' => [
'input_tokens' => 123,
'output_tokens' => 88,
'total_tokens' => 211,
],
'tool_calls' => [],
'raw' => null,
'meta' => [
'cached' => false,
'cache_key' => '...',
'duration_ms' => 123,
],
]This response model gives application code one stable place to read:
- the resolved profile,
- the provider name,
- the model name,
- the assistant message,
- the finish reason,
- token usage,
- tool calls,
- optional raw provider data,
- and operational metadata such as cache state and duration.
HelloAi is configured under the helloai node. The important concept is the profile: the application selects a profile, and the profile resolves to a concrete adapter and endpoint configuration.
<?php
declare(strict_types=1);
return [
'helloai' => [
'default_profile' => 'dev',
'debug' => [
'include_raw_response' => false,
'include_built_request' => false,
],
'cache' => [
'enabled' => true,
'ttl' => 3600,
],
'profiles' => [
'dev' => [
'adapter' => \CitOmni\HelloAi\Provider\Dev\DevAdapter::class,
'model' => 'dev',
'api_key' => '',
'base_url' => 'https://www.citomni.com/helloai/dev-endpoint.php',
'timeout' => 30,
],
'claude-sonnet-4-6' => [
'adapter' => \CitOmni\HelloAi\Provider\Anthropic\AnthropicAdapter::class,
'model' => 'claude-sonnet-4-6',
'api_key' => '...',
'base_url' => 'https://api.anthropic.com/v1/messages',
'timeout' => 30,
],
],
],
];default_profileis used when the request does not provide one.- Each profile points to an adapter class.
- Multiple profiles may reuse the same adapter.
- Each profile carries the concrete model, endpoint, credentials, and timeout.
- General transport defaults belong to the existing cURL configuration, not to a duplicated HelloAi transport subtree.
Adapters are the translation boundary between HelloAi's internal format and each provider's native API contract.
An adapter is responsible for:
- building the outbound provider request,
- building provider-specific headers,
- decoding the provider response body,
- normalizing the result into HelloAi's common response structure,
- and raising provider-appropriate exceptions on malformed or failed responses.
The adapter contract is intentionally small:
interface AdapterInterface {
public function buildRequest(array $request): array;
public function buildHeaders(array $request): array;
public function parseResponse(array $transportResult, array $request): array;
}This keeps provider logic sharply localized and prevents transport, caching, or profile selection concerns from bleeding into adapter implementations.
HelloAi delegates HTTP execution to the existing CitOmni cURL service:
$this->app->curl->execute(array $request): arrayThat service remains the transport authority. It validates transport request shape, performs the HTTP call, applies timeout and SSL behavior, and returns the raw transport result. HelloAi does not replace or duplicate that layer. Instead, it builds the cURL request array, delegates execution, and lets the adapter decode and interpret the response body.
This separation is consequential:
- HelloAi does not centralize provider JSON parsing.
- Adapters decode provider JSON where appropriate.
- Transport exceptions remain transport exceptions.
- HelloAi may add context in logs, but it does not need a parallel transport abstraction to do so.
HelloAi includes a database-backed cache accessed through HelloAiCacheRepository. Cache keys are built deterministically from the normalized request payload, specifically from:
profilemessagesoptionstoolsprovider_options
If those inputs are identical after normalization, the request is considered identical for caching purposes.
A typical cache entry contains:
cache_keyprofilemodelrequest_payloadresponse_payloadcreated_atexpires_at
The operational rule is simple:
- cache lookup happens before any external API call,
- successful normalized responses may be stored,
- invalid requests, provider failures, transport failures, and parse failures are not cached.
HelloAi logs through the existing CitOmni log service. Logged context is designed to be operationally useful without casually exposing credentials.
The package sanitizes secrets such as:
api_keyAuthorizationx-api-key- related authentication headers and tokens.
Useful HelloAi log context typically includes:
- profile,
- model,
- adapter class,
- cache hit or miss,
- cache key,
- duration,
- status code,
- sanitized request and response context,
- and exception details where relevant.
Because the underlying cURL service may also log, HelloAi should be used with a conscious logging policy to avoid redundant noise.
HelloAi also exposes a command-line entry point:
php bin/citomni helloai:chat "Hello. Who are you?" --profile="claude-haiku-4-5"The command accepts the user prompt as a required argument and supports optional profile, system, developer, debug, JSON, temperature, and max-output-tokens flags. In plain mode it prints the assistant text and then a compact info line containing the resolved profile, provider, model, and cache status.
Application code should target profile ids rather than provider-specific endpoints or model payload formats directly. This keeps provider translation localized to adapters and configuration.
Cache identity is derived from the normalized request payload. If profile, messages, options, tools, or provider_options differ, the request is treated as distinct for cache purposes.
HelloAi does not replace the CitOmni cURL service. Transport validation, HTTP execution, timeout handling, and low-level request mechanics remain delegated to the existing infrastructure layer.
HelloAi logs package-level events such as cache hits/misses, adapter selection, sanitized request/response context, status information, and failures. Since the underlying cURL service may also log, production installations should choose a deliberate logging policy rather than accidentally generating duplicate transport noise.
The package is organized around a small set of clear responsibilities:
src/
Boot/
Command/
Exception/
Interface/
Provider/
Repository/
Service/
Util/
A representative structure includes:
Boot/Registry.phpfor service/config/CLI registration,Service/HelloAi.phpas the primary orchestration service,Interface/AdapterInterface.phpfor adapter contracts,Provider/...for concrete provider adapters,Repository/HelloAiCacheRepository.phpfor cache persistence,Util/CacheKeyBuilder.phpandUtil/SecretSanitizer.phpfor focused helper logic,Exception/...for package-specific domain and adapter exceptions.
- Services are resolved through explicit service maps rather than scanning.
- Provider translation is localized to small adapters with a narrow contract.
- Deterministic caching reduces repeated external API calls for identical normalized requests.
- The package reuses existing infrastructure services rather than layering a second transport stack on top.
- Production should use optimized Composer autoloading.
- OPcache should be enabled in production.
Composer example:
{
"config": {
"optimize-autoloader": true,
"classmap-authoritative": true,
"apcu-autoloader": true
}
}Then run:
composer dump-autoload -oHelloAi defines its own domain exception family for request, profile, adapter, provider, and unsupported-feature failures. Transport failures from the underlying cURL service remain the responsibility of that service and are not arbitrarily rewrapped into a redundant transport hierarchy. This keeps fault boundaries explicit and preserves the original transport semantics.
Fail fast.
HelloAi does not hide malformed requests, profile misconfiguration, unsupported features, provider parse failures, or transport failures behind vague fallback behavior. Invalid structure should fail as invalid structure; provider-specific problems should fail as provider-specific problems; transport problems should remain transport problems.
That bias is deliberate. In integration code, silent fallback logic often looks convenient right up until it becomes the reason nobody can tell what actually happened.
<?php
declare(strict_types=1);
$response = $this->app->helloAi->chat([
'profile' => 'claude-sonnet-4-6',
'messages' => [
[
'role' => 'system',
'content' => [
['type' => 'text', 'text' => 'You are a concise technical writer.'],
],
],
[
'role' => 'user',
'content' => [
['type' => 'text', 'text' => 'Explain cache invalidation in two paragraphs.'],
],
],
],
'options' => [
'temperature' => 0.3,
'max_output_tokens' => 300,
],
]);
$text = $response['message']['content'][0]['text'] ?? '';HelloAi follows the broader CitOmni philosophy: explicit contracts, deterministic behavior, small public surfaces, and low overhead. It does one thing narrowly but well: it gives CitOmni applications a disciplined and reusable way to talk to chatbot providers without forcing application code to speak every provider's dialect.
- PHP 8.2+
- PSR-4
- Tabs for indentation
- K&R brace style
- Keep ownership boundaries sharp
- Keep SQL in repositories, transport in controllers, orchestration in operations
- Do not introduce magic or hidden fallback behavior in auth-critical flows without an explicit and documented reason
All CitOmni projects follow the shared conventions documented here: CitOmni Coding & Documentation Conventions
CitOmni Authenticate is open-source under the MIT License. See LICENSE.
Trademark notice: "CitOmni" and the CitOmni logo are trademarks of Lars Grove Mortensen. Usage of the name or logo must follow the policy in NOTICE. Do not imply endorsement or affiliation without prior written permission.
"CitOmni" and the CitOmni logo are trademarks of Lars Grove Mortensen. You may make factual references to "CitOmni", but do not modify the marks, create confusingly similar logos, or imply sponsorship, endorsement, or affiliation without prior written permission. Do not register or use "citomni" (or confusingly similar terms) in company names, domains, social handles, or top-level vendor/package names. For details, see NOTICE.
Developed by Lars Grove Mortensen © 2012-present.
CitOmni - low overhead, high performance, ready for anything.