LLM::Cache
Constants
0700, and entries below are written 0600 (see store). A cache entry
is the model's answer about a specific chunk of the user's source, and
the prompt it answers is that source — private code, on a box that may
have other accounts. The default 0755/0644 published it to every local
user. $NOIR_HOME/config.yaml is already 0600 for the same reason.
All cache entries are stored as <sha256>.json flat in
cache_dir. The bulk operations below (clear, purge_older_than,
stats) filter on this suffix so a stray .lock or user-dropped
file in the directory is left alone.
Infix store stamps onto its in-flight temp file (see store).
Kept as a constant so the writer and the sweepers below can't drift
apart: a stranded temp file whose name the sweepers don't recognize
is a file nothing can ever delete.
The prompts themselves are part of every key. Bump this when a change outside them (adapter-side wrapping, how a reply is read) should stop older cached replies from being replayed.
Class methods
Build a deterministic cache key from inputs
- provider: "openai", "ollama", url, etc.
- model: "gpt-4o", "llama3", etc.
- kind: logical operation e.g. "FILTER", "ANALYZE", "BUNDLE_ANALYZE"
- format: response_format string (e.g., "json" or JSON schema string)
- payload: variable content (file list, source code, bundle, etc.)
The sampling settings are folded in too, so a reply sampled at another temperature or seed is not replayed.
Returns a hex-encoded SHA256 digest.
Where the swallowed cache failures below are reported.
They used to go to Crystal's global Log at debug level, which was
wrong twice over: that logger's default backend writes to STDOUT,
the stream carrying the -f json / -f sarif report, and nothing in
Noir ever lowers its Info threshold, so the messages could not
surface at all. A diagnostic that is simultaneously invisible and
aimed at the report stream is the worst of both.
NoirLogger (STDERR, gated on --debug) is where every other Noir
diagnostic goes. Nil until a run installs one, so a library caller
constructing no logger stays silent exactly as before.
Write atomically: a partially-written file from a crash mid-write
would parse as broken JSON on the next fetch, forcing a
spurious fresh API call. By writing to a tmp sibling and renaming
we either leave the previous (valid) entry in place or atomically
publish the new one.
True for a temp file stranded by a crash between File.write and
File.rename. These do not end in .json, so the bulk operations
used to skip them entirely — clear could not remove them and
stats did not count their bytes, leaving files that grew the cache
directory with no way to reclaim them from the CLI.