Ctrl K
ComunidadGuías…

tools/llm_check.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env python3
"""The provider-agnostic LLM client: right request shape, and a real SSRF gate.

No network and no keys needed — the request shaping and response parsing are
pure, and the destination guard is exercised with IP literals so it resolves
offline and deterministically. What matters here is that a prompt and a key are
refused before they can be POSTed to a cloud metadata endpoint or an internal
host, and that no error message ever carries the key.

    python3 tools/llm_check.py

Exit status is 0 on pass, 1 on fail.
"""

from __future__ import annotations

import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
from clique import llm


def _raises(fn) -> bool:
    try:
        fn()
    except llm.LLMError:
        return True
    return False


def main() -> int:
    res: dict[str, object] = {}

    # OpenAI dialect: key rides in the Bearer header, path and body as expected.
    oai = {
        "kind": "openai",
        "base_url": "https://api.example.com/v1/",
        "model": "gpt-x",
        "key": "sk-K",
    }
    url, headers, body = llm._request_for(oai, [{"role": "user", "content": "hi"}], 128)
    res["openai_path"] = url == "https://api.example.com/v1/chat/completions"
    res["openai_auth"] = headers.get("Authorization") == "Bearer sk-K"
    res["openai_body"] = (
        body["model"] == "gpt-x" and body["max_tokens"] == 128 and bool(body["messages"])
    )

    # Anthropic dialect: x-api-key + version, and the system turn is lifted out.
    ant = {
        "kind": "anthropic",
        "base_url": "https://api.anthropic.com",
        "model": "claude-x",
        "key": "sk-A",
    }
    msgs = [{"role": "system", "content": "be brief"}, {"role": "user", "content": "hi"}]
    url, headers, body = llm._request_for(ant, msgs, 64)
    res["anthropic_path"] = url == "https://api.anthropic.com/v1/messages"
    res["anthropic_auth"] = headers.get("x-api-key") == "sk-A" and "anthropic-version" in headers
    res["anthropic_system_split"] = body.get("system") == "be brief" and all(
        m["role"] != "system" for m in body["messages"]
    )

    # Misconfiguration is refused, not guessed at.
    res["unknown_kind_raises"] = _raises(lambda: llm._request_for({"kind": "nope"}, [], 8))
    res["no_base_raises"] = _raises(
        lambda: llm._request_for({"kind": "openai", "model": "m"}, [], 8)
    )
    res["no_model_raises"] = _raises(
        lambda: llm._request_for({"kind": "openai", "base_url": "https://x"}, [], 8)
    )

    # Response parsing pulls the text out of either shape.
    res["parse_openai"] = (
        llm._text_of("openai", {"choices": [{"message": {"content": "hello"}}]}) == "hello"
    )
    res["parse_anthropic"] = (
        llm._text_of("anthropic", {"content": [{"type": "text", "text": "hello"}]}) == "hello"
    )
    res["parse_bad_shape_raises"] = _raises(lambda: llm._text_of("openai", {"nope": 1}))

    # The SSRF gate: the whole point of the module's safety.
    res["blocks_metadata"] = _raises(lambda: llm._guard_url("http://169.254.169.254/latest/meta"))
    res["blocks_private"] = _raises(lambda: llm._guard_url("http://10.0.0.5/v1"))
    res["blocks_non_http"] = _raises(lambda: llm._guard_url("ftp://example.com/x"))
    # Loopback (local models like Ollama) and public hosts are allowed through.
    try:
        llm._guard_url("http://127.0.0.1:11434/v1/chat/completions")
        llm._guard_url("https://1.1.1.1/v1/chat/completions")
        res["allows_loopback_and_public"] = True
    except llm.LLMError:
        res["allows_loopback_and_public"] = False

    # complete() must run the gate BEFORE any socket work, and the resulting
    # error must not leak the key that sat in the headers.
    danger = {
        "kind": "openai",
        "base_url": "http://169.254.169.254",
        "model": "m",
        "key": "sk-LEAK",
    }
    try:
        llm.complete(danger, [{"role": "user", "content": "hi"}], timeout=1)
        res["complete_guards_first"] = False
        res["error_hides_key"] = False
    except llm.LLMError as exc:
        res["complete_guards_first"] = "blocked address" in str(exc)
        res["error_hides_key"] = "sk-LEAK" not in str(exc)

    ok = all(res.values())
    for key, value in res.items():
        print(f"  {'ok  ' if value else 'FAIL'} {key}: {value}")
    print("llm_check:", "PASS" if ok else "FAIL")
    return 0 if ok else 1


if __name__ == "__main__":
    sys.exit(main())