UQLM Scores How Much You Should Trust an LLM Answer

Analysis by the aitrendblend editorial team · 9 min read · Updated July 2026
uncertainty quantification hallucination detection LangChain Python package practical AI tools
Diagram style illustration of a language model answer being scored for confidence by an uncertainty quantification pipeline
UQLM turns a single generated answer into a confidence score between zero and one, without needing a ground truth reference at generation time.
A support engineer pastes a customer question into a chatbot built on GPT or Claude, gets back a clean, confident sounding paragraph, and has no real way to know whether that paragraph is grounded in fact or quietly invented. That gap between how confident a language model sounds and how confident it actually is has become one of the most stubborn problems in deploying these systems anywhere that matters. A team at CVS Health has just published a Python package aimed squarely at closing it.

Key points

  • UQLM is an open source Python package for scoring the reliability of a language model response at the moment it is generated, with no ground truth answer required.
  • It combines four families of scoring technique in one library, black box consistency checks, white box token probability methods, LLM as a judge panels, and tunable ensembles of all three.
  • Every scorer outputs a confidence value between zero and one, and the package plugs directly into any LangChain compatible chat model, which covers OpenAI, Anthropic, Google, Cohere, AWS Bedrock, and locally hosted models like Llama and Mistral.
  • The ensemble approach can be tuned against a small set of graded examples, letting a team optimize the weighting for their own accuracy target rather than relying on defaults.
  • The work was published in the Journal of Machine Learning Research and the code is public on GitHub under a permissive license.

The problem this actually solves

Ask a large language model a factual question and it will almost always answer in the same fluent, self assured tone whether it knows the answer or is guessing. Researchers call the guessing case a hallucination, a generated statement that is false or misleading but reads as if it were fact. The paper’s authors, led by Dylan Bouchard at CVS Health, frame the stakes plainly. In healthcare, legal, and financial settings, a hallucinated answer that looks exactly like a correct one is far more dangerous than an answer that visibly hedges.

The standard way to catch these errors is to grade a model’s output against a human written ground truth answer, the approach used by tools such as OpenAI’s Evals and G Eval. That works well during a pre deployment test cycle, when you already have a labeled dataset sitting next to your prompts. It falls apart the moment a real user sends a live question that nobody wrote a reference answer for in advance, which is most of the time a production system is actually running.

So the interesting engineering problem is not grading against a known answer. It is estimating, at the instant a model produces a response and with nothing else to compare it to, how much that response should be trusted. That is the definition of generation time hallucination detection, and it is the specific gap UQLM was built to fill.

Where the existing tools fall short

The paper is refreshingly specific about what already exists and where each approach runs out of road. Source comparison toolkits such as Ragas, Arize Phoenix, and DeepEval check whether a generated answer is consistent with the input prompt or retrieved context. That catches a model contradicting its own source material, but it will happily approve an answer that just parrots the prompt’s phrasing without being factually correct, since agreement with the prompt is not the same thing as agreement with reality.

Internet grounded checkers like FacTool go the other direction and verify claims against a live search. That adds real latency to every response and introduces a new failure mode, since the search results themselves can be wrong or out of date, which defeats the purpose of trying to reduce uncertainty.

Then there is the uncertainty quantification literature itself, which is large and mathematically rich but has mostly stayed inside research code. SelfCheckGPT popularized the idea of sampling a model multiple times and checking whether the answers agree with each other, but its toolkit only covers a narrow slice of the available techniques and does not tie generation and scoring together in one workflow. LM Polygraph, built on the Hugging Face ecosystem, is a genuinely strong collection of UQ methods, and the UQLM authors credit it as an important step, while noting that it still asks more of a non specialist practitioner than a small team building a product feature usually has time for.

UQLM’s pitch is that a team should not have to choose between a narrow, easy tool and a comprehensive, hard to adopt one. It packages a wide range of established UQ techniques from the academic literature behind a small, consistent API, and it fuses response generation with scoring so a developer calls one method and gets both the answer and its confidence score back together.

Why this matters in practice A team shipping a customer facing assistant does not usually have a labeled answer key sitting behind every possible user question. What they can afford is a scoring layer that flags the ten percent of responses most likely to be wrong so a human reviews those, rather than reviewing everything or nothing.

How UQLM is put together

The library organizes its scorers into four families, and the design choice that ties them together is worth noting before the details. Every class in the package, whether it belongs to the black box, white box, judge, or ensemble family, is built by handing it a LangChain chat model object and calling a generate and score method. That single method both produces the model’s response and computes its confidence score, which is what the authors mean when they say the package integrates generation with evaluation rather than treating them as two separate steps bolted together afterward.

Black box scoring

Black box methods do not look inside the model at all. They exploit the fact that language models are stochastic, meaning the same prompt sent twice can produce two different answers. UQLM generates an original response plus several additional candidate responses to the same prompt, then measures how much those responses agree with each other using a specified metric. The package supports several ways of measuring that agreement, including discrete semantic entropy, counting the number of distinct semantic clusters among the responses, non contradiction probability, entailment probability, BERTScore similarity, an exact match rate, and cosine similarity between response embeddings.

The intuition is straightforward once you see it. If a model is asked the same factual question five times and gives five different answers, that disagreement is itself a signal the model does not actually know the answer, regardless of how confident any single response sounds. The tradeoff is cost. Generating multiple responses to the same prompt means multiple calls to the underlying model, so black box scoring is compatible with any chat model but adds both latency and spend.

White box scoring

White box methods go the opposite direction and reach directly into the token probabilities the model produces while generating, which means they only work with APIs that actually expose log probabilities. The package’s single generation methods, including minimum token probability, length normalized token probability, sequence probability, likelihood margin, and top K token entropy measures, cost nothing extra since they are computed from a response you were already generating anyway. A second set of sampling based white box methods, including semantic entropy, semantic density, Monte Carlo predictive entropy, and a method called CoCoA, trade that speed advantage for typically stronger signal by drawing on multiple samples the same way the black box family does.

One additional white box technique worth calling out is the P(True) method, which asks the model a follow up question about whether its own prior answer was correct and treats the probability it assigns to yes as a further confidence signal. It needs one extra generation per response, a modest cost for what is effectively asking the model to self assess.

LLM as a judge

The third family sidesteps token probabilities entirely and instead asks one or more separate language models to grade a question and answer pair directly, using the LLMPanel class. A developer can register several judge models at once and choose from four scoring templates, a simple binary correct or incorrect scale, a three point scale that allows for an uncertain middle option, a continuous scale from zero to one, and a five point Likert scale. Custom instructions can be layered in through an additional context argument, which is useful when a team wants judges to apply domain specific correctness criteria rather than generic ones. Running several judges at once and looking at their average, minimum, and median gives a more stable read than trusting any single judge’s opinion.

Ensembles

The fourth and arguably most practical piece is the UQEnsemble class, which combines scorers from any of the previous three families into one weighted score. Out of the box, with no configuration, it defaults to a combination of exact match, non contradiction probability, and a self judge approach drawn from earlier work by Chen and Mueller. A team that wants better performance for their own use case can instead specify which scorers to include and then call a tune method, handing it a set of prompts paired with ideal answers. UQLM grades the model’s responses against that answer key, either with a grader function the team supplies or by falling back to the same LLM acting as its own grader, and then runs an optimization routine that solves for the scorer weights that best separate correct answers from incorrect ones.

Once the individual scorers have each produced a value between zero and one for a response, the ensemble combines them as a weighted average. \[ C = \frac{\sum_{i=1}^{k} w_i \cdot s_i}{\sum_{i=1}^{k} w_i} \] Here \(s_i\) is the confidence score from scorer \(i\), and \(w_i\) is that scorer’s weight, either left at the library defaults, set by hand, or solved for during the tuning step described above.

That optimization can target different objectives depending on what a team cares about. A threshold agnostic objective like ROC AUC is useful when the downstream decision about where to draw the line between trusted and flagged is still open. A threshold dependent objective like F1 score is the better fit once a team has already committed to a specific cutoff, for example flagging anything scoring below 0.4 for human review. Once the weights are solved, they get stored on the ensemble object and reused for every future call, so the tuning step is something a team runs once against a small labeled set and then leaves alone.

The most useful thing about treating confidence as a tunable weighted score, rather than a fixed rule, is that a team gets to decide what kind of mistake they would rather make. Editorial observation, aitrendblend

What it actually costs to run each family

The paper includes a compatibility table in its appendix that is genuinely more useful for a planning conversation than any benchmark number would be, because it tells you upfront what each approach demands from your infrastructure before you write a line of code.

Scorer familyNeeds token log probabilitiesModel compatibility
Black box UQNoWorks with any chat model, since it only needs multiple generated responses to compare
White box UQYesLimited to chat models whose API actually exposes log probabilities
LLM as a JudgeNoWorks with any chat model, since judging is itself just another generation task
UQ EnsembleDependsDetermined by whichever individual scorers are included in the ensemble

That log probability requirement is a more practical constraint than it might sound. Plenty of hosted APIs either do not return token probabilities at all or gate them behind specific request parameters, so a team building on top of a provider that does not expose them is effectively locked out of the entire white box family and needs to lean on black box consistency checks or judge panels instead. UQLM does not hide that limitation, it documents it directly and points developers to LangChain’s own guidance on which chat model wrappers support requesting log probabilities.

Practical takeaway Before choosing a scorer family, check whether your model provider exposes log probabilities through its API. If it does not, the white box methods are simply not available to you, no matter how appealing their zero extra cost profile looks on paper.

What this changes for teams shipping LLM products

The broader implication here is less about any single scoring technique and more about what it means for uncertainty quantification to finally show up as a well packaged software dependency rather than a research paper’s supplementary code. A method like semantic entropy has existed in the literature since Farquhar and colleagues published it in Nature in 2024, but a team without a research background historically had to translate that paper into working code themselves. UQLM’s contribution is mostly one of accessibility, taking a body of techniques scattered across a dozen papers and putting them behind one consistent interface that plugs into infrastructure teams already use.

That accessibility argument matters more in smaller organizations than large ones. A well resourced lab can afford to build internal tooling around any one of these techniques. A three person startup shipping a customer support bot cannot, and is far more likely to either skip confidence scoring entirely or bolt on something shallow like a fixed keyword filter. Lowering the engineering cost of a real uncertainty estimate changes the calculus for exactly that smaller team.

It is also worth being honest about what this kind of scoring does not do. A confidence score is a signal, not a guarantee. A high score means the model was internally consistent and its own probability estimates were strong, which correlates with correctness but is not the same thing as correctness. Teams that treat a UQLM score as a hard pass or fail gate without any human review in the loop for high stakes decisions are trading one kind of blind trust for another, just with better math underneath it.

Second takeaway Uncertainty quantification narrows down where human attention should go, it does not remove the need for that attention on decisions with real consequences.

Honest limitations

A few constraints are worth flagging plainly rather than glossing over. The paper itself is short, four pages of core content plus appendices, and it functions as a software announcement rather than a full empirical study. Detailed experiment results and head to head comparisons against the scorer families are pushed to a separate companion paper the same authors published, so a reader wanting hard numbers on which scorer performs best under which conditions needs to go read that second paper rather than expecting them here.

The tuning workflow also has a chicken and egg quality to it. Getting a well calibrated ensemble requires a set of prompts with known correct answers, which is exactly the kind of labeled data that is scarce for the live, unpredictable queries a production system actually receives. Teams will likely end up tuning against a curated historical sample and hoping it generalizes to new traffic, which is a reasonable approach but not a guarantee.

Finally, all four authors are employed by CVS Health and hold equity in the company, a conflict of interest the paper discloses directly. That does not undermine the technical content, which draws on peer reviewed methods and is independently verifiable through the public GitHub repository, but it is relevant context for readers evaluating the source.

A working reimplementation of the ensemble idea

To make the ensemble concept concrete, the code below is an original, simplified reimplementation of the core idea described in the paper, a weighted combination of a black box style consistency scorer and a white box style token probability scorer, tuned against a small labeled set. It is written for learning purposes and is not the UQLM library’s actual source code, which is publicly available at the GitHub link below if you want the real implementation.

# Educational reimplementation of the UQLM ensemble concept.
# Not the actual UQLM source, written to illustrate the approach described in the paper.
import torch
import torch.nn as nn
import torch.optim as optim

class ConfidenceEnsemble(nn.Module):
    """
    Combines a black box style consistency score and a white box style
    token probability score into one weighted confidence value, then
    lets the weights be tuned against labeled examples.
    """
    def __init__(self, num_scorers=2):
        super().__init__()
        # start with equal weights, softmax keeps them positive and summing to one
        self.raw_weights = nn.Parameter(torch.zeros(num_scorers))

    def forward(self, scorer_outputs):
        # scorer_outputs shape is (batch, num_scorers), each value already in [0, 1]
        weights = torch.softmax(self.raw_weights, dim=0)
        confidence = (scorer_outputs * weights).sum(dim=-1)
        return confidence


def black_box_consistency_score(candidate_embeddings):
    """
    Simple stand in for a black box scorer. Given several response
    embeddings for the same prompt, returns the average pairwise
    cosine similarity as a rough consistency estimate.
    """
    normed = nn.functional.normalize(candidate_embeddings, dim=-1)
    sims = normed @ normed.transpose(-1, -2)
    n = sims.shape[-1]
    mask = ~torch.eye(n, dtype=torch.bool, device=sims.device)
    return sims[..., mask].view(sims.shape[0], n, n - 1).mean(dim=(-2, -1))


def white_box_min_probability_score(token_log_probs):
    """
    Stand in for a white box scorer, the minimum token probability
    across the generated sequence, which is highly sensitive to a
    single weak or unlikely token anywhere in the response.
    """
    return token_log_probs.exp().min(dim=-1).values


def train_ensemble(model, scorer_outputs, labels, epochs=200, lr=0.05):
    """
    Tunes the ensemble weights against labeled examples, playing the
    role of UQLM's tune method. Labels are 1.0 for a correct response
    and 0.0 for an incorrect one, gathered from a small answer key.
    """
    optimizer = optim.Adam(model.parameters(), lr=lr)
    loss_fn = nn.BCELoss()

    for epoch in range(epochs):
        optimizer.zero_grad()
        predictions = model(scorer_outputs)
        loss = loss_fn(predictions, labels)
        loss.backward()
        optimizer.step()

    return model


def evaluate(model, scorer_outputs, labels, threshold=0.5):
    """Reports simple accuracy at a chosen confidence threshold."""
    with torch.no_grad():
        predictions = model(scorer_outputs)
        predicted_labels = (predictions > threshold).float()
        accuracy = (predicted_labels == labels).float().mean().item()
    return accuracy


if __name__ == "__main__":
    # Smoke test on dummy data, standing in for a real batch of prompts
    torch.manual_seed(0)
    batch_size = 64
    num_candidates = 5
    embedding_dim = 16
    seq_len = 12

    candidate_embeddings = torch.randn(batch_size, num_candidates, embedding_dim)
    token_log_probs = torch.log(torch.rand(batch_size, seq_len).clamp(min=1e-4))

    black_box_scores = black_box_consistency_score(candidate_embeddings)
    white_box_scores = white_box_min_probability_score(token_log_probs)
    scorer_outputs = torch.stack([black_box_scores, white_box_scores], dim=-1)

    dummy_labels = (scorer_outputs.mean(dim=-1) > 0.5).float()

    ensemble = ConfidenceEnsemble(num_scorers=2)
    ensemble = train_ensemble(ensemble, scorer_outputs, dummy_labels)

    acc = evaluate(ensemble, scorer_outputs, dummy_labels)
    learned_weights = torch.softmax(ensemble.raw_weights, dim=0).detach()

    print(f"Smoke test accuracy on dummy data: {acc:.3f}")
    print(f"Learned scorer weights: {learned_weights.tolist()}")

Conclusion

UQLM’s core achievement is not any single scoring technique, most of which were already published elsewhere. It is the packaging decision to put black box consistency checks, white box token probability methods, LLM judge panels, and tunable ensembles behind one small, consistent interface, and to tie response generation and confidence scoring together into a single method call instead of two separate systems a team has to wire up themselves.

That packaging choice represents a real conceptual shift in how uncertainty quantification gets adopted. Research code that demonstrates a technique works is a different artifact from production code a small team can drop into an existing LangChain pipeline on a Tuesday afternoon. UQLM sits firmly in the second category, and that is precisely the gap the authors identify in their survey of prior tools, where methods like LM Polygraph offer real depth but ask more setup effort than many teams can spare.

The transferability of this approach reaches beyond the chatbot use case the paper leans on for its motivating example. Any pipeline where a language model produces a claim that downstream code or a human will act on, a summarization tool, an automated report generator, a code review assistant, faces the same fundamental problem of not knowing when to trust a fluent looking output. A confidence scoring layer that plugs into any LangChain compatible model is portable across all of those situations with essentially no rework.

The honest limitations are still worth carrying forward. This is a young library announced in a short paper, its ensemble tuning depends on labeled data that will often be scarce, and a high confidence score is correlated with correctness rather than a guarantee of it. Teams adopting UQLM should treat it as a triage tool that directs human review toward the riskiest outputs, not as a replacement for that review on decisions where being wrong actually costs something.

Where this goes next probably depends less on new scoring math and more on integration depth, tighter hooks into observability platforms, broader coverage of hosted APIs that still do not expose log probabilities, and real world calibration studies showing how these scores hold up outside the lab. For now, UQLM’s real contribution is making a genuinely useful piece of research infrastructure something a working engineer can install and use before lunch.

Read the full paper and explore the open source code.
Read the paper View the GitHub repo

Frequently asked questions

What does UQLM actually stand for

UQLM stands for Uncertainty Quantification for Language Models. It is the name of both the research paper and the open source Python package it introduces.

Does UQLM need a ground truth answer to work

No, and that is its main selling point. UQLM estimates a confidence score at generation time using the model’s own outputs and token probabilities, so it does not require a pre written correct answer for the specific question being asked, though tuning the ensemble weights does require a small labeled set.

Which language models work with UQLM

Any chat model wrapped by LangChain’s chat model interface, which covers commercial providers such as OpenAI, Anthropic, Google, Cohere, and AWS Bedrock, plus locally hosted open source models like Llama, Mistral, and Qwen run through tools such as Ollama.

Why do some scoring methods not work with every model

The white box scoring family reads token probabilities directly from the model’s output, and some hosted APIs do not expose those probabilities through their interface. Black box scoring and LLM as a judge scoring do not have this restriction since they only need generated text, not internal probabilities.

Is UQLM free and open source

Yes. The paper is published under a CC BY 4.0 license and the code is publicly available on GitHub under CVS Health’s organization account.

Can UQLM guarantee a response is correct

No. It produces a confidence score that correlates with correctness based on internal consistency and probability signals, which is useful for prioritizing human review, but it is not a factual verification system and can be wrong.

Bouchard, D., Chauhan, M. S., Skarbrevik, D., Ra, H., Bajaj, V., and Ahmad, Z. UQLM, A Python Package for Uncertainty Quantification in Large Language Models. Journal of Machine Learning Research, 27:1 to 10, 2026. Available at jmlr.org/papers/v27/25-1557.html.
This analysis is based on the published paper and an independent evaluation of its claims.

Related reading

Leave a Comment

Your email address will not be published. Required fields are marked *