Skip to content
Nibiru docsv0.9.2

The AI module

First-class AI in Nibiru — chat, embeddings, RAG, agents — wired to your own Ollama on your Ollama instance. No paid APIs required.

Stable Reading time ~ 4 min Edit on GitHub

Nibiru ships an AI module (application/module/ai/) that gives every Nibiru app a first-class AI surface. PHP code can chat with a local LLM, embed text, run RAG over its own data, or run an agent with tools — all without sending a byte to a paid API.

The module is wired to your own Ollama on your Ollama instance by default, so inference is on your hardware, on your network, on your terms.

PluginWhat it doesOne-liner
ChatChat completions, single- or multi-turn$ai->chat()->ask('…')
EmbedText → vectors + cosine helpers$ai->embed()->one('…')
RagIngest + retrieve + grounded chat$ai->rag('docs')->ask('…')
AgentTool-using ReAct loop$ai->agent()->withTools([…])->run('…')
ToolBase for your own custom toolsclass MyTool extends Tool { … }
OllamaRaw HTTP transport to any Ollama-compatible endpoint(new Ollama($cfg))->chat(…)
use Nibiru\Module\Ai\Ai;
$ai = new Ai();
echo $ai->chat()->ask('How do I scaffold a new module?');
// → "Run `./nibiru -m <name>`. This creates application/module/<name>/ with…"

That’s the whole API surface for the simple case. No DI container, no API keys, no SDK install.

Every plugin reads its settings from application/module/ai/settings/ai.ini:

[AI]
ollama.base_url = "https://your-ollama-host.example"
chat.model = "nibiru-coder:1.0"
chat.fallback_model = "qwen2.5-coder:14b"
chat.temperature = 0.4
chat.max_tokens = 1024
embed.model = "nomic-embed-text"
rag.top_k = 6
agent.max_iterations = 6

Per-environment override: ai.production.ini, ai.staging.ini. Nibiru’s Registry auto-discovers them.

$ai = new \Nibiru\Module\Ai\Ai();
// One-shot
echo $ai->chat()->ask('Explain MMVC in two sentences.');
// Multi-turn
$chat = $ai->chat();
$chat->user('How do I scaffold a module?');
$chat->user('And add Graylog hooks?'); / referrs to previous turn
echo $chat->complete();
// Override per call
echo $ai->chat()
->system('Answer in German.')
->model('qwen2.5-coder:14b')
->temperature(0.1)
->ask('Was ist ein Modul?');

The Chat plugin auto-falls back to chat.fallback_model if the primary model isn’t available — useful while you’re still building nibiru-coder.

$embed = $ai->embed();
$va = $embed->one('controller');
$vb = $embed->one('module');
$score = \Nibiru\Module\Ai\Plugin\Embed::cosine($va, $vb);
// → 0.78 (close concepts)

Compact storage:

$packed = \Nibiru\Module\Ai\Plugin\Embed::pack($vec); / base64 string, 4 bytes/dim
$vec = \Nibiru\Module\Ai\Plugin\Embed::unpack($packed);
$rag = $ai->rag('product-help');
// One-time ingestion
$rag->ingestDir(__DIR__ . '/help/'); / walks .md/.txt/.php
$rag->ingestText('FAQ entry…', ['source' => 'faq-12']);
$rag->ingestFile('/var/data/manual.pdf.txt');
// Then ask grounded questions
echo $rag->ask('How do I cancel my subscription?');
// → "Per the help docs, you can cancel in account → settings… [1]"

Storage: a single JSON file per collection at application/module/ai/cache/rag/<name>.json. Restartable, zero DB, fits ~10k chunks comfortably in memory.

use Nibiru\Module\Ai\Plugin\Tools;
$ai = new \Nibiru\Module\Ai\Ai();
$agent = $ai->agent()->withTools([
new Tools\PdoQuery(), / read-only SQL
new Tools\HttpGet(), / fetch URLs
new Tools\FileRead(), / read project files
]);
echo $agent->run('How many active users registered last week?');
// → agent decides to call pdo_query with SELECT count(*) FROM users…
// reads observation, writes a final answer.

The agent uses a ReAct-style loop: read task → pick tool → execute → observe → repeat → final answer. The protocol uses a simple \“tool {…}```` JSON sentinel that works on every Ollama model — no model-specific tool-calling APIs required.

application/module/ai/
├── ai.php # main class implementing IModule
├── interfaces/ai.php # contract
├── traits/ai.php # cfg() helper
├── plugins/
│ ├── ollama.php # raw transport
│ ├── chat.php # chat completions
│ ├── embed.php # embeddings + cosine + pack
│ ├── rag.php # ingest + retrieve + grounded chat
│ ├── agent.php # ReAct tool loop
│ ├── tool.php # abstract base for custom tools
│ └── tools/
│ ├── pdoQuery.php # read-only SQL
│ ├── httpGet.php # HTTP GET
│ └── fileRead.php # project-local file read
├── settings/ai.ini # config
├── cache/rag/ # RAG vector index files (gitignored)
└── training/
├── Modelfile # the nibiru-coder system prompt
├── build.sh # one-command Modelfile → registered model
├── smoke-test.php # verify the whole stack
└── README.md # training pipeline guide

PHP doesn’t have an established “AI framework” the way Python has LangChain or JS has Vercel AI SDK. Nibiru’s AI module fills that gap with the smallest, sharpest API we could write — three layers (transport → plugin → module), no DI graph, no SDK install, no per-token bill.

The design philosophy:

  • Bring your own brain. Ollama by default, Anthropic and OpenAI as drop-ins. Swap providers via INI, never via code.
  • One JSON file per RAG collection. No vector DB. Reboot-safe. Grep-able when you’re debugging.
  • Tools are PHP classes. Extend Tool, get a name + schema + execute method. The agent figures out the rest.
  • No model-specific tool-call APIs. A single fenced-JSON convention works everywhere.