"fast", "max_tokens" => $max, "messages" => [["role" => "user", "content" => $question]]]); [$s, $out] = call_app("ai", "/?api=complete", true, "POST", $body, ["Content-Type: application/json"], 60); $r = json_decode($out, true); if ($s !== 200 || !is_array($r)) return ["error" => ($r["error"]["message"] ?? "ai answered $s"), "status" => $s === 429 ? 429 : 502]; $text = trim(implode("", array_map(fn($b) => $b["type"] === "text" ? $b["text"] : "", $r["content"] ?? []))); return ["answer" => $text, "via" => $r["rc"]["via"] ?? "", "for" => $r["rc"]["app"] ?? "", "model" => $r["model"] ?? "", "tokens" => ($r["usage"]["input_tokens"] ?? 0) + ($r["usage"]["output_tokens"] ?? 0)]; } return [ "n" => 14, "try" => "ask the model a question", "wing" => "Intelligence", "kind" => "self", "title" => "Ask a model", "promise" => "Any app can ask a language model a question without holding a key.", "block" => 'The ai app holds the key; an app calls it with rc_app_headers("ai", true) and gets an answer marked who asked, for whom.', "show" => function (?array $me): string { if (!$me) return '
Sign in to ask the model something.
'; return 'One question at a time, up to 500 characters; each answer goes on your ledger (exhibit 15).
' . ''; }, "api" => function (string $do, ?array $me, array $in, bool $post): ?array { if ($do !== "ask" || !$post) return null; if (!$me) return ["error" => "sign in first", "status" => 401]; $q = trim(mb_substr((string)($in["question"] ?? ""), 0, 500)); if ($q === "") return ["error" => "ask something", "status" => 400]; return ask_ai($q, min(400, max(1, (int)($in["max_tokens"] ?? 400)))); }, // The robot asks a fixed cheap question; and nothing key-shaped may sit in what we serve. "check" => function (): array { require_once ZOO_ROOT . "/lib/robot.php"; [$code, , $j] = robot_at_zoo()->post("https://" . env("RC_HOST") . "/api/14/ask", ["question" => "Reply with the single word: pong", "max_tokens" => 20]); if ($code !== 200 || !is_array($j)) return [false, "the question failed ($code): " . (is_array($j) ? ($j["error"] ?? "") : substr((string)$j, 0, 120))]; if ($j["answer"] === "") return [false, "ai answered, but with nothing"]; if ($j["via"] !== "ai" || $j["for"] !== "zoo") return [false, "the answer is not marked 'via ai, for zoo' (via '{$j['via']}', for '{$j['for']}')"]; $it = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(ZOO_ROOT, FilesystemIterator::SKIP_DOTS)); foreach ($it as $f) { if (!preg_match('/\.(php|js|css|conf|md)$/', $f->getFilename()) || str_contains($f->getPathname(), "/.git/")) continue; if (preg_match('/\bsk-[A-Za-z0-9_-]{20,}|\b[0-9a-f]{32}\.[A-Za-z0-9]{12,}|-----BEGIN [A-Z ]*PRIVATE KEY/', (string)file_get_contents($f->getPathname()))) return [false, "something key-shaped is in " . substr($f->getPathname(), strlen(ZOO_ROOT) + 1)]; } return [true, "asked the fast model through ai as the robot: \"" . mb_substr($j["answer"], 0, 40) . "\" ({$j['tokens']} tokens); no key in the zoo's files", ["tokens" => $j["tokens"]]]; }, "script" => <<<'JS' document.getElementById("ask-form")?.addEventListener("submit", async (ev) => { ev.preventDefault(); const out = document.getElementById("ask-out"), btn = ev.target.querySelector("button"); btn.disabled = true; out.textContent = "thinking…"; const j = await zoo.call("/api/14/ask", { question: ev.target.q.value }); btn.disabled = false; if (j.error) { out.textContent = j.error; return; } out.innerHTML = 'answered via ' + zoo.esc(j.via) + ', for ' + zoo.esc(j.for) + ', on your behalf (' + zoo.esc(j.model) + ', ' + j.tokens + ' tokens)
'; window.dispatchEvent(new Event("zoo-asked")); }); JS, ];