pub struct LlmClient { /* private fields */ }Expand description
A configured LLM client ready to issue requests.
Built once per process from LlmConfig; complete_json is the only
entry point the analyzer uses.
Fields are pub(crate) so the test submodules can construct clients
with a non-default retry config (the production default sleeps 1s
between attempts, which would make the retry tests take seconds). They
are not part of the public API.
Implementations§
Source§impl LlmClient
impl LlmClient
Sourcepub fn model(&self) -> &str
pub fn model(&self) -> &str
The model this client asks for.
An accessor rather than a second copy on the caller: the cache key is
computed from the model, and a struct holding its own model string is
exactly what lets a request go to one model while the key names
another.
Sourcepub fn endpoint(&self) -> &str
pub fn endpoint(&self) -> &str
The base URL this client talks to. For display - doctor and the
failover report name the endpoint a provider used.
Sourcepub fn temperature(&self) -> Option<f32>
pub fn temperature(&self) -> Option<f32>
The sampling temperature, or None when none is sent. Part of the cache
key, for the same reason Self::model is - and None has to key
differently from any value, because the answers genuinely differ.
Sourcepub fn request_identity(&self) -> &str
pub fn request_identity(&self) -> &str
Every HTTP request option that can change the answer while the endpoint and model stay the same.
Header names are canonicalised to lower case because HTTP compares them case-insensitively. Values remain exact: an arbitrary header can select a tenant, model route or feature variant, and drep cannot infer from its name whether changing it changes the answer. The returned digest is fed into the cache hash and is never logged.
Sourcepub fn protocol(&self) -> ApiProtocol
pub fn protocol(&self) -> ApiProtocol
The wire protocol this client speaks. For display, and for the cache key: the same model at the same endpoint over two protocols is two requests.
Sourcepub fn new(cfg: &LlmConfig) -> Result<Self, LlmError>
pub fn new(cfg: &LlmConfig) -> Result<Self, LlmError>
Build a client from a validated LlmConfig.
Returns LlmError::NotConfigured when the config does not name an
endpoint, a model, or has enabled = false. We do not default an
endpoint - “LLM was disabled” and “LLM is enabled but misconfigured”
are both fatal here, and inventing a value would mask a broken
install.
Sourcepub async fn complete_json(
&self,
system_prompt: &str,
user_content: &str,
) -> Result<Extracted, LlmError>
pub async fn complete_json( &self, system_prompt: &str, user_content: &str, ) -> Result<Extracted, LlmError>
Send one prompt and return the extracted JSON.
Concatenates ContentBlock::Text blocks in arrival order; other block
variants are ignored. An empty response body is retried as a
transport failure and, if it keeps coming back empty, surfaces as
LlmError::Transport - see run_one_query for why “the model
returned nothing” is not the deterministic outcome it looks like.
A non-empty body that yields no JSON at all is retried up to
NO_JSON_ATTEMPTS times and then becomes LlmError::Unparseable,
carrying an excerpt of what actually came back. A body that parsed only
after brace-balancing (Extracted::Truncated) is returned
immediately and never retried - that is the genuinely deterministic
case, and the one the “never retry” rule was written for.