Initial public push: docs cosmos v4 + AI module + framework groundwork
This is the snapshot the production landing site (nibiru-framework.com) is deployed from. Brings together the recent splash + docs migration to the v4 "Cosmos" design system, the new in-framework AI module, and the framework groundwork that backs the framework-reference extraction. What lands: - docs/: Astro + Starlight site with the v4 dark cosmic palette, GalaxyHero canvas constellation, Mission Control chat (wired to /api/oracle → api.neuronetz.ai via providers.mjs Ollama), 5-panel MMVC stage (Model · AI · Module · Controller · View), translated EN/DE/JA/ES/FR content, PWA + sitemap + llms.txt + Umami analytics. - docs/design-system/: canonical mockup bundle (source/index-v2.html for splash, source/docs-system.html + preview/ for docs, SPEC.md, tokens). - docs/scripts/extraction/framework-reference-v2.md: deep framework reference (~1.6k lines, file:line citations, every public factory and idiom — basis for the LoRA training corpus. - application/module/ai/: AI module with chat / embed / RAG / agent plugins, plus pdoQuery / httpGet / fileRead tools and Modelfile + smoke-test in training/. - application/module/users/: user / ACL / form-factory traits used as the reference plugin pattern for the framework docs. - application/settings/config/database/: schema + seed migrations including the AI module tables (200–203). - Form factory + autogenerator changes the framework-reference-v2 covers. Production secrets stay out: docs/.env, settings.production.ini and ai.production.ini are all gitignored (.example files are in tree). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
172
application/module/ai/plugins/ollama.php
Normal file
172
application/module/ai/plugins/ollama.php
Normal file
@@ -0,0 +1,172 @@
|
||||
<?php
|
||||
namespace Nibiru\Module\Ai\Plugins;
|
||||
|
||||
/**
|
||||
* Ollama HTTP transport. The lowest layer of the AI stack — every other
|
||||
* plugin (Chat, Embed, RAG, Agent) ultimately speaks through this.
|
||||
*
|
||||
* Works against any Ollama-compatible endpoint (localhost:11434, a
|
||||
* remote Ollama via reverse proxy, vLLM with the /api/chat shim,
|
||||
* LiteLLM proxies, etc.).
|
||||
*/
|
||||
class Ollama
|
||||
{
|
||||
protected string $baseUrl;
|
||||
protected int $timeout;
|
||||
protected int $retries;
|
||||
|
||||
public function __construct(\stdClass $cfg)
|
||||
{
|
||||
$this->baseUrl = rtrim((string) ($cfg->ollama_base_url ?? 'http://localhost:11434'), '/');
|
||||
$this->timeout = (int) ($cfg->ollama_timeout ?? 90);
|
||||
$this->retries = (int) ($cfg->ollama_retries ?? 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/chat — synchronous, non-streaming.
|
||||
*
|
||||
* @param string $model e.g. "nibiru-coder:1.0"
|
||||
* @param array $messages [{role: 'user'|'assistant'|'system', content: '...'}]
|
||||
* @param array $options {temperature, num_predict, top_p, ...}
|
||||
* @return array decoded JSON response
|
||||
*/
|
||||
public function chat(string $model, array $messages, array $options = []): array
|
||||
{
|
||||
return $this->post('/api/chat', [
|
||||
'model' => $model,
|
||||
'messages' => $messages,
|
||||
'stream' => false,
|
||||
'options' => $options,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/embeddings — single input.
|
||||
*
|
||||
* @return array{embedding: float[]}
|
||||
*/
|
||||
public function embed(string $model, string $prompt): array
|
||||
{
|
||||
return $this->post('/api/embeddings', [
|
||||
'model' => $model,
|
||||
'prompt' => $prompt,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/generate — single-prompt completion (no chat structure).
|
||||
*/
|
||||
public function generate(string $model, string $prompt, array $options = []): array
|
||||
{
|
||||
return $this->post('/api/generate', [
|
||||
'model' => $model,
|
||||
'prompt' => $prompt,
|
||||
'stream' => false,
|
||||
'options' => $options,
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/tags — list available models.
|
||||
*/
|
||||
public function listModels(): array
|
||||
{
|
||||
return $this->get('/api/tags');
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/ps — list currently-loaded models.
|
||||
*/
|
||||
public function listLoaded(): array
|
||||
{
|
||||
return $this->get('/api/ps');
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/pull — pull a model onto the Ollama server.
|
||||
*/
|
||||
public function pull(string $name): array
|
||||
{
|
||||
return $this->post('/api/pull', ['name' => $name, 'stream' => false]);
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /api/create — register a custom model from a Modelfile.
|
||||
*/
|
||||
public function create(string $name, string $modelfile): array
|
||||
{
|
||||
return $this->post('/api/create', [
|
||||
'name' => $name,
|
||||
'modelfile' => $modelfile,
|
||||
'stream' => false,
|
||||
]);
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// HTTP helpers
|
||||
// -----------------------------------------------------------------------
|
||||
|
||||
protected function post(string $path, array $body): array
|
||||
{
|
||||
$url = $this->baseUrl . $path;
|
||||
$payload = json_encode($body, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE);
|
||||
return $this->withRetries(function () use ($url, $payload) {
|
||||
$ch = curl_init($url);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_POST => true,
|
||||
CURLOPT_POSTFIELDS => $payload,
|
||||
CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => $this->timeout,
|
||||
CURLOPT_CONNECTTIMEOUT => 10,
|
||||
]);
|
||||
$body = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$err = curl_error($ch);
|
||||
curl_close($ch);
|
||||
if ($body === false) {
|
||||
throw new \RuntimeException("Ollama transport error: $err");
|
||||
}
|
||||
if ($code >= 400) {
|
||||
$snip = substr((string) $body, 0, 240);
|
||||
throw new \RuntimeException("Ollama HTTP $code: $snip");
|
||||
}
|
||||
$decoded = json_decode((string) $body, true);
|
||||
if (!is_array($decoded)) {
|
||||
throw new \RuntimeException('Ollama returned non-JSON body.');
|
||||
}
|
||||
return $decoded;
|
||||
});
|
||||
}
|
||||
|
||||
protected function get(string $path): array
|
||||
{
|
||||
$ch = curl_init($this->baseUrl . $path);
|
||||
curl_setopt_array($ch, [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_TIMEOUT => 15,
|
||||
CURLOPT_CONNECTTIMEOUT => 5,
|
||||
]);
|
||||
$body = curl_exec($ch);
|
||||
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if ($body === false || $code >= 400) {
|
||||
throw new \RuntimeException("Ollama GET $path failed (HTTP $code)");
|
||||
}
|
||||
return json_decode((string) $body, true) ?? [];
|
||||
}
|
||||
|
||||
protected function withRetries(callable $fn)
|
||||
{
|
||||
$last = null;
|
||||
for ($i = 0; $i <= $this->retries; $i++) {
|
||||
try {
|
||||
return $fn();
|
||||
} catch (\RuntimeException $e) {
|
||||
$last = $e;
|
||||
if ($i < $this->retries) usleep(250_000 * (1 << $i)); // 250ms, 500ms, ...
|
||||
}
|
||||
}
|
||||
throw $last ?? new \RuntimeException('Ollama call failed.');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user