chat()->ask('Explain the dispatcher in two sentences.'); * * $rag = $ai->rag('docs'); * $rag->ingest(__DIR__ . '/../../view/templates/'); * echo $rag->ask('Where is the login form built?'); * * $agent = $ai->agent()->withTools([ * new Plugins\Tools\PdoQuery(), * new Plugins\Tools\HttpGet(), * ]); * echo $agent->run('How many active users do we have?'); * * Configuration in application/module/ai/settings/ai.ini. * * @author Stephan Kasdorf * @license BSD */ use Nibiru\Module\Ai\Interfaces; use Nibiru\Module\Ai\Traits; use Nibiru\Module\Ai\Plugins; use Nibiru\Registry; use SplSubject; use SplObserver; use SplObjectStorage; class Ai implements Interfaces\Ai, SplSubject { use Traits\Ai; const CONFIG_MODULE_NAME = 'ai'; /** @var \stdClass module config from settings/ai.ini */ protected static ?\stdClass $aiRegistry = null; /** @var SplObjectStorage observer storage */ protected SplObjectStorage $observers; /** @var Plugins\Chat|null lazy chat plugin */ protected ?Plugins\Chat $chatPlugin = null; /** @var Plugins\Embed|null lazy embed plugin */ protected ?Plugins\Embed $embedPlugin = null; /** @var array RAG instances keyed by collection name */ protected array $ragInstances = []; public function __construct() { $this->setAiRegistry(); $this->observers = new SplObjectStorage(); } public function attach(SplObserver $observer): void { $this->observers->attach($observer); } public function detach(SplObserver $observer): void { $this->observers->detach($observer); } public function notify(): void { foreach ($this->observers as $observer) { $observer->update($this); } } /** * Get a chat-completion plugin. */ public function chat(): Plugins\Chat { return $this->chatPlugin ??= new Plugins\Chat($this->config()); } /** * Get an embedding plugin. */ public function embed(): Plugins\Embed { return $this->embedPlugin ??= new Plugins\Embed($this->config()); } /** * Get a named RAG (Retrieval-Augmented Generation) collection. Each * collection has its own on-disk JSON vector index, so you can have * one RAG over your docs, another over your error logs, another over * customer-support tickets, all in the same app. */ public function rag(string $collection = 'default'): Plugins\Rag { return $this->ragInstances[$collection] ??= new Plugins\Rag( $collection, $this->config(), $this->chat(), $this->embed() ); } /** * Get an agent with optional tools attached. Agents call the LLM * iteratively with tool-call decoding until they hit a terminal answer. */ public function agent(): Plugins\Agent { return new Plugins\Agent($this->config(), $this->chat()); } /** * The active config (a stdClass populated from settings/ai.ini). */ public function config(): \stdClass { return self::$aiRegistry; } protected function setAiRegistry(): void { if (self::$aiRegistry === null) { $cfg = Registry::getInstance()->loadModuleConfigByName(self::CONFIG_MODULE_NAME); self::$aiRegistry = $cfg ?: new \stdClass(); } } }