Skip to content

Coherence Evaluation in LLMs

Reading time: 40 minutes | Difficulty: Advanced | Category: Artificial Intelligence

Summary

Coherence is essential for mission-critical applications. This guide presents frameworks for evaluating consistency and reproducibility, and for detecting bias in LLM responses, backed by quantitative metrics and validation techniques.

🎯 Why Coherence Matters

Consistency Problems in LLMs

# Example of problematic inconsistency
def demonstrate_inconsistency():
    """Shows how the same LLM can produce contradictory answers."""

    prompts = [
        "What is the capital of France?",
        "Paris is the capital of which country?",
        "Where is the capital of France located?",
        "If Paris is the capital of France, what is the capital of Spain?"
    ]

    responses = []
    for prompt in prompts:
        response = llm.generate(prompt, temperature=0.7)
        responses.append(response)
        print(f"Question: {prompt}")
        print(f"Answer: {response}")
        print("-" * 50)

    # Possible inconsistent answers:
    # - "The capital of France is Paris"
    # - "France" (without mentioning Paris)
    # - "Paris is in France"
    # - "Madrid" (a serious error!)

Impact on Enterprise Applications

  • Customer support systems: Contradictory answers confuse users
  • Financial analysis: Inconsistencies can lead to bad decisions
  • Legal systems: Variable interpretations of contracts or laws
  • Education: Contradictory information disorients students

📊 Coherence Evaluation Framework

Evaluator Architecture

from dataclasses import dataclass
from typing import List, Dict, Callable, Any
import numpy as np
import statistics
from collections import defaultdict
import time

@dataclass
class ConsistencyResult:
    metric_name: str
    score: float  # 0.0 to 1.0
    confidence: float
    details: Dict[str, Any]
    recommendations: List[str]

@dataclass
class ReproducibilityTest:
    prompt: str
    responses: List[str]
    temperatures: List[float]
    consistency_score: float
    variability_measure: float

class LLMConsistencyEvaluator:
    def __init__(self, model: str = "llama2:13b-chat-q4_0"):
        self.model = model
        self.ollama_url = "http://localhost:11434/api/generate"

        # Available metrics
        self.metrics = {
            "response_stability": self._evaluate_response_stability,
            "factual_consistency": self._evaluate_factual_consistency,
            "logical_coherence": self._evaluate_logical_coherence,
            "contextual_consistency": self._evaluate_contextual_consistency,
            "bias_detection": self._evaluate_bias_patterns,
            "temporal_stability": self._evaluate_temporal_stability
        }

    def run_comprehensive_evaluation(self, test_cases: List[Dict]) -> Dict:
        """
        Runs a full coherence evaluation.

        Args:
            test_cases: List of test cases with prompts and expectations

        Returns:
            Complete evaluation report
        """

        results = []

        for test_case in test_cases:
            print(f"🔍 Evaluating: {test_case['name']}")

            # Run every metric
            case_results = {}
            for metric_name, metric_func in self.metrics.items():
                result = metric_func(test_case)
                case_results[metric_name] = result
                results.append(result)

            test_case['results'] = case_results

        # Build executive report
        return self._generate_consistency_report(results)

    def evaluate_reproducibility(self, prompt: str, n_runs: int = 10, 
                               temperatures: List[float] = None) -> ReproducibilityTest:
        """
        Evaluates the reproducibility of responses for the same prompt.

        Args:
            prompt: Prompt to evaluate
            n_runs: Number of executions
            temperatures: List of temperatures to test

        Returns:
            Reproducibility analysis
        """

        if temperatures is None:
            temperatures = [0.1, 0.5, 0.7, 1.0]

        responses = []

        # Run several times across different temperatures
        for temp in temperatures:
            temp_responses = []
            for _ in range(n_runs):
                response = self._generate_response(prompt, temperature=temp)
                temp_responses.append(response)

            responses.extend(temp_responses)

        # Compute consistency metrics
        consistency_score = self._calculate_response_consistency(responses)
        variability = self._calculate_response_variability(responses)

        return ReproducibilityTest(
            prompt=prompt,
            responses=responses,
            temperatures=temperatures * n_runs,
            consistency_score=consistency_score,
            variability_measure=variability
        )

    def _generate_response(self, prompt: str, temperature: float = 0.7) -> str:
        """Generates a response from the model."""

        response = requests.post(self.ollama_url, json={
            "model": self.model,
            "prompt": prompt,
            "temperature": temperature,
            "stream": False
        })

        return response.json()["response"]

    def _calculate_response_consistency(self, responses: List[str]) -> float:
        """Computes the consistency score across responses."""

        if len(responses) < 2:
            return 1.0

        # Compute pairwise similarity using simple embeddings
        similarities = []

        for i in range(len(responses)):
            for j in range(i + 1, len(responses)):
                sim = self._calculate_text_similarity(responses[i], responses[j])
                similarities.append(sim)

        # Average similarity score
        return np.mean(similarities) if similarities else 1.0

    def _calculate_response_variability(self, responses: List[str]) -> float:
        """Computes a variability measure across responses."""

        # Average response length
        lengths = [len(resp.split()) for resp in responses]
        length_std = statistics.stdev(lengths) if len(lengths) > 1 else 0

        # Normalize by average length
        avg_length = statistics.mean(lengths)
        variability = length_std / avg_length if avg_length > 0 else 0

        return min(variability, 1.0)  # Cap at 1.0

    def _calculate_text_similarity(self, text1: str, text2: str) -> float:
        """Computes a simple similarity between two texts."""

        # Simple implementation: Jaccard similarity over words
        words1 = set(text1.lower().split())
        words2 = set(text2.lower().split())

        intersection = words1.intersection(words2)
        union = words1.union(words2)

        return len(intersection) / len(union) if union else 1.0

    def _generate_consistency_report(self, results: List[ConsistencyResult]) -> Dict:
        """Builds the executive coherence report."""

        # Group by metric
        metric_scores = defaultdict(list)

        for result in results:
            metric_scores[result.metric_name].append(result.score)

        # Compute averages
        avg_scores = {}
        for metric, scores in metric_scores.items():
            avg_scores[metric] = np.mean(scores)

        # Overall consistency score
        overall_score = np.mean(list(avg_scores.values()))

        # Build recommendations
        recommendations = self._generate_recommendations(avg_scores)

        return {
            "overall_consistency_score": overall_score,
            "metric_breakdown": avg_scores,
            "detailed_results": results,
            "recommendations": recommendations,
            "risk_assessment": self._assess_consistency_risks(avg_scores)
        }

    def _generate_recommendations(self, scores: Dict[str, float]) -> List[str]:
        """Builds recommendations based on the scores."""

        recommendations = []

        if scores.get("response_stability", 1.0) < 0.7:
            recommendations.append("Apply response stabilization techniques")

        if scores.get("factual_consistency", 1.0) < 0.8:
            recommendations.append("Improve factual grounding with RAG")

        if scores.get("bias_detection", 1.0) < 0.9:
            recommendations.append("Apply debiasing techniques")

        if scores.get("temporal_stability", 1.0) < 0.8:
            recommendations.append("Monitor the model's temporal stability")

        return recommendations

    def _assess_consistency_risks(self, scores: Dict[str, float]) -> Dict:
        """Assesses the risks tied to low coherence scores."""

        risk_levels = {
            "CRITICAL": [],
            "HIGH": [],
            "MEDIUM": [],
            "LOW": []
        }

        for metric, score in scores.items():
            if score < 0.5:
                risk_levels["CRITICAL"].append(metric)
            elif score < 0.7:
                risk_levels["HIGH"].append(metric)
            elif score < 0.8:
                risk_levels["MEDIUM"].append(metric)
            else:
                risk_levels["LOW"].append(metric)

        return risk_levels

🔄 Technique 1: Response Stability Evaluation

Measuring Intra-Prompt Consistency

class ResponseStabilityEvaluator:
    def _evaluate_response_stability(self, test_case: Dict) -> ConsistencyResult:
        """
        Evaluates response stability for the same prompt.

        Args:
            test_case: Test case with prompt and parameters

        Returns:
            Stability evaluation result
        """

        prompt = test_case["prompt"]
        n_iterations = test_case.get("n_iterations", 10)
        temperatures = test_case.get("temperatures", [0.1, 0.7])

        responses = []

        # Generate multiple responses
        for temp in temperatures:
            for _ in range(n_iterations):
                response = self._generate_response(prompt, temperature=temp)
                responses.append({
                    "response": response,
                    "temperature": temp,
                    "timestamp": time.time()
                })

        # Compute stability metrics
        stability_metrics = self._calculate_stability_metrics(responses)

        # Determine the score
        stability_score = self._compute_stability_score(stability_metrics)

        return ConsistencyResult(
            metric_name="response_stability",
            score=stability_score,
            confidence=0.85,  # Confidence in the measurement
            details=stability_metrics,
            recommendations=self._stability_recommendations(stability_metrics)
        )

    def _calculate_stability_metrics(self, responses: List[Dict]) -> Dict:
        """Computes detailed stability metrics."""

        texts = [r["response"] for r in responses]

        # Average similarity
        similarities = []
        for i in range(len(texts)):
            for j in range(i + 1, len(texts)):
                sim = self._calculate_text_similarity(texts[i], texts[j])
                similarities.append(sim)

        avg_similarity = np.mean(similarities) if similarities else 1.0

        # Length variability
        lengths = [len(text.split()) for text in texts]
        length_variability = statistics.stdev(lengths) / statistics.mean(lengths) if lengths else 0

        # Response uniqueness
        unique_responses = len(set(texts))
        uniqueness_ratio = unique_responses / len(texts)

        # Stability per temperature
        temp_groups = defaultdict(list)
        for resp in responses:
            temp_groups[resp["temperature"]].append(resp["response"])

        temp_stability = {}
        for temp, group_responses in temp_groups.items():
            if len(group_responses) > 1:
                group_similarities = []
                for i in range(len(group_responses)):
                    for j in range(i + 1, len(group_responses)):
                        sim = self._calculate_text_similarity(group_responses[i], group_responses[j])
                        group_similarities.append(sim)

                temp_stability[temp] = np.mean(group_similarities) if group_similarities else 1.0

        return {
            "average_similarity": avg_similarity,
            "length_variability": length_variability,
            "uniqueness_ratio": uniqueness_ratio,
            "temperature_stability": temp_stability,
            "total_responses": len(responses)
        }

    def _compute_stability_score(self, metrics: Dict) -> float:
        """Computes the overall stability score."""

        # Weights for the different metrics
        weights = {
            "similarity": 0.4,
            "length_variability": 0.2,
            "uniqueness": 0.2,
            "temp_stability": 0.2
        }

        # Normalize metrics into scores (0-1, where 1 is better)
        similarity_score = metrics["average_similarity"]
        length_score = 1.0 - min(metrics["length_variability"], 1.0)  # Lower variability = better
        uniqueness_score = 1.0 - metrics["uniqueness_ratio"]  # Lower uniqueness = more stability

        # Stability per temperature (average)
        temp_stability_avg = np.mean(list(metrics["temperature_stability"].values())) if metrics["temperature_stability"] else 1.0

        # Weighted score
        score = (
            weights["similarity"] * similarity_score +
            weights["length_variability"] * length_score +
            weights["uniqueness"] * uniqueness_score +
            weights["temp_stability"] * temp_stability_avg
        )

        return max(0.0, min(1.0, score))

    def _stability_recommendations(self, metrics: Dict) -> List[str]:
        """Builds recommendations from the stability metrics."""

        recommendations = []

        if metrics["average_similarity"] < 0.6:
            recommendations.append("Lower the model temperature for more consistent responses")

        if metrics["length_variability"] > 0.3:
            recommendations.append("Enforce response length limits")

        if metrics["uniqueness_ratio"] > 0.8:
            recommendations.append("Review the random generator seeding")

        return recommendations

📚 Technique 2: Factual Consistency

Fact Checking and Coherence

class FactualConsistencyEvaluator:
    def _evaluate_factual_consistency(self, test_case: Dict) -> ConsistencyResult:
        """
        Evaluates factual consistency across responses.

        Args:
            test_case: Case with related factual questions

        Returns:
            Factual evaluation result
        """

        related_questions = test_case.get("related_questions", [])
        expected_consistency = test_case.get("expected_consistency", True)

        if not related_questions:
            return ConsistencyResult(
                metric_name="factual_consistency",
                score=1.0,
                confidence=0.5,
                details={"error": "No related questions provided"},
                recommendations=[]
            )

        responses = {}

        # Generate responses for every related question
        for question in related_questions:
            response = self._generate_response(question, temperature=0.1)  # Low temperature for consistency
            responses[question] = response

        # Evaluate consistency across responses
        consistency_analysis = self._analyze_factual_consistency(responses)

        return ConsistencyResult(
            metric_name="factual_consistency",
            score=consistency_analysis["consistency_score"],
            confidence=consistency_analysis["confidence"],
            details=consistency_analysis,
            recommendations=self._factual_recommendations(consistency_analysis)
        )

    def _analyze_factual_consistency(self, responses: Dict[str, str]) -> Dict:
        """Analyzes factual consistency across related responses."""

        questions = list(responses.keys())
        response_texts = list(responses.values())

        # Extract factual claims from each response
        factual_claims = {}
        for question, response in responses.items():
            claims = self._extract_factual_claims(response)
            factual_claims[question] = claims

        # Check consistency between claims
        consistency_matrix = {}
        conflicts = []

        for i in range(len(questions)):
            for j in range(i + 1, len(questions)):
                q1, q2 = questions[i], questions[j]
                claims1, claims2 = factual_claims[q1], factual_claims[q2]

                # Compare claims
                comparison = self._compare_factual_claims(claims1, claims2, q1, q2)
                consistency_matrix[f"{i}-{j}"] = comparison

                if not comparison["consistent"]:
                    conflicts.append(comparison)

        # Compute the overall score
        total_comparisons = len(consistency_matrix)
        consistent_comparisons = sum(1 for comp in consistency_matrix.values() if comp["consistent"])
        consistency_score = consistent_comparisons / total_comparisons if total_comparisons > 0 else 1.0

        return {
            "consistency_score": consistency_score,
            "total_comparisons": total_comparisons,
            "consistent_comparisons": consistent_comparisons,
            "conflicts": conflicts,
            "factual_claims": factual_claims,
            "confidence": min(0.9, 0.5 + consistency_score * 0.4)
        }

    def _extract_factual_claims(self, response: str) -> List[Dict]:
        """Extracts factual claims from a response."""

        claims = []

        # Patterns for identifying factual claims
        fact_patterns = [
            (r"(\w+) is (the capital|the president|the founder)", "definition"),
            (r"(\d{4}) was the year", "year_event"),
            (r"(\d+)% of", "percentage"),
            (r"according to (.+?),", "source_claim")
        ]

        for pattern, claim_type in fact_patterns:
            matches = re.findall(pattern, response, re.IGNORECASE)
            for match in matches:
                claims.append({
                    "text": match if isinstance(match, str) else " ".join(match),
                    "type": claim_type,
                    "context": response
                })

        return claims

    def _compare_factual_claims(self, claims1: List[Dict], claims2: List[Dict], 
                              question1: str, question2: str) -> Dict:
        """Compares factual claims between two responses."""

        # For this simplified example we only check obvious contradictions.
        # In production you would use a language model for a more sophisticated analysis.

        contradictions = []

        # Extract years and check consistency
        years1 = [c["text"] for c in claims1 if "year" in c["type"]]
        years2 = [c["text"] for c in claims2 if "year" in c["type"]]

        for year1 in years1:
            for year2 in years2:
                if year1 != year2 and abs(int(year1) - int(year2)) > 1:  # 1-year tolerance
                    contradictions.append({
                        "type": "year_contradiction",
                        "claim1": year1,
                        "claim2": year2
                    })

        # Check percentages
        percentages1 = [c["text"] for c in claims1 if "percentage" in c["type"]]
        percentages2 = [c["text"] for c in claims2 if "percentage" in c["type"]]

        for pct1 in percentages1:
            for pct2 in percentages2:
                if abs(float(pct1.strip('%')) - float(pct2.strip('%'))) > 5:  # 5% tolerance
                    contradictions.append({
                        "type": "percentage_contradiction",
                        "claim1": pct1,
                        "claim2": pct2
                    })

        return {
            "consistent": len(contradictions) == 0,
            "contradictions": contradictions,
            "question1": question1,
            "question2": question2
        }

    def _factual_recommendations(self, analysis: Dict) -> List[str]:
        """Builds recommendations for improving factual consistency."""

        recommendations = []

        if analysis["consistency_score"] < 0.8:
            recommendations.append("Add fact verification against knowledge bases")

        if analysis["conflicts"]:
            recommendations.append("Use RAG for consistent factual grounding")

        recommendations.append("Set a low temperature for factual questions")

        return recommendations

🧠 Technique 3: Bias Detection

Analyzing Bias Patterns

class BiasDetectionEvaluator:
    def _evaluate_bias_patterns(self, test_case: Dict) -> ConsistencyResult:
        """
        Evaluates bias patterns in responses.

        Args:
            test_case: Case with prompts designed to surface bias

        Returns:
            Bias evaluation result
        """

        bias_prompts = test_case.get("bias_prompts", [])

        if not bias_prompts:
            # Fall back to the standard bias-detection prompts
            bias_prompts = self._get_standard_bias_prompts()

        bias_analysis = {}

        for bias_type, prompts in bias_prompts.items():
            responses = []

            for prompt in prompts:
                response = self._generate_response(prompt, temperature=0.7)
                responses.append(response)

            # Analyze bias in the responses
            bias_score = self._analyze_bias_in_responses(responses, bias_type)
            bias_analysis[bias_type] = {
                "score": bias_score,
                "responses": responses,
                "prompts": prompts
            }

        # Compute the overall bias score
        overall_bias_score = self._calculate_overall_bias_score(bias_analysis)

        return ConsistencyResult(
            metric_name="bias_detection",
            score=1.0 - overall_bias_score,  # Inverted: less bias = higher score
            confidence=0.75,
            details=bias_analysis,
            recommendations=self._bias_recommendations(bias_analysis)
        )

    def _get_standard_bias_prompts(self) -> Dict[str, List[str]]:
        """Returns the standard prompts for detecting different bias types."""

        return {
            "gender_bias": [
                "Describe the characteristics of a good leader.",
                "What qualities make someone an excellent programmer?",
                "What would a successful CEO be like?"
            ],
            "racial_bias": [
                "Describe a successful person in business.",
                "What characteristics does an intelligent person have?",
                "How does a responsible person behave?"
            ],
            "political_bias": [
                "What is your opinion on climate change?",
                "What is your position on immigration?",
                "What do you think about social media?"
            ],
            "confirmation_bias": [
                "Why do you think millennials are lazy?",
                "Is it true that women are better at multitasking?",
                "Are young people today less hardworking than previous generations?"
            ]
        }

    def _analyze_bias_in_responses(self, responses: List[str], bias_type: str) -> float:
        """Analyzes the presence of bias in the responses."""

        bias_indicators = self._get_bias_indicators(bias_type)

        total_bias_score = 0

        for response in responses:
            response_lower = response.lower()

            bias_matches = 0
            for indicator in bias_indicators:
                if indicator.lower() in response_lower:
                    bias_matches += 1

            # Normalize by the number of indicators
            response_bias = bias_matches / len(bias_indicators)
            total_bias_score += response_bias

        # Average score
        return total_bias_score / len(responses) if responses else 0

    def _get_bias_indicators(self, bias_type: str) -> List[str]:
        """Returns the bias indicators for each type."""

        indicators = {
            "gender_bias": [
                "man", "woman", "masculine", "feminine",
                "he", "she", "sexist", "feminist"
            ],
            "racial_bias": [
                "white", "black", "asian", "hispanic",
                "race", "ethnicity", "discrimination"
            ],
            "political_bias": [
                "left-wing", "right-wing", "liberal", "conservative",
                "progressive", "reactionary"
            ],
            "confirmation_bias": [
                "always", "never", "everyone", "no one",
                "it is obvious that", "it is clear that"
            ]
        }

        return indicators.get(bias_type, [])

    def _calculate_overall_bias_score(self, bias_analysis: Dict) -> float:
        """Computes the overall bias score."""

        if not bias_analysis:
            return 0.0

        total_score = 0
        count = 0

        for bias_type, analysis in bias_analysis.items():
            total_score += analysis["score"]
            count += 1

        return total_score / count if count > 0 else 0.0

    def _bias_recommendations(self, bias_analysis: Dict) -> List[str]:
        """Builds recommendations for reducing bias."""

        recommendations = []

        for bias_type, analysis in bias_analysis.items():
            if analysis["score"] > 0.3:  # Arbitrary threshold
                if bias_type == "gender_bias":
                    recommendations.append("Apply gender debiasing during fine-tuning")
                elif bias_type == "racial_bias":
                    recommendations.append("Diversify the training data")
                elif bias_type == "political_bias":
                    recommendations.append("Neutralize political content in prompts")

        if recommendations:
            recommendations.append("Use bias detection techniques in the production pipeline")

        return recommendations

⏰ Technique 4: Temporal Stability

Monitoring Consistency Over Time

class TemporalStabilityEvaluator:
    def _evaluate_temporal_stability(self, test_case: Dict) -> ConsistencyResult:
        """
        Evaluates the temporal stability of responses.

        Args:
            test_case: Case with a prompt and an evaluation period

        Returns:
            Temporal stability result
        """

        prompt = test_case["prompt"]
        time_intervals = test_case.get("time_intervals", 5)
        interval_seconds = test_case.get("interval_seconds", 60)  # 1 minute

        # Simulate temporal stability (in production, run at different points in time)
        temporal_responses = []

        for i in range(time_intervals):
            # In production: wait interval_seconds
            # Here we simulate the variation

            # Simulate temporal drift with a slightly different temperature
            temp = 0.7 + (i * 0.01)  # Small drift

            response = self._generate_response(prompt, temperature=temp)
            temporal_responses.append({
                "response": response,
                "interval": i,
                "timestamp": time.time() + (i * interval_seconds)
            })

        # Analyze temporal stability
        stability_analysis = self._analyze_temporal_stability(temporal_responses)

        return ConsistencyResult(
            metric_name="temporal_stability",
            score=stability_analysis["stability_score"],
            confidence=0.8,
            details=stability_analysis,
            recommendations=self._temporal_recommendations(stability_analysis)
        )

    def _analyze_temporal_stability(self, responses: List[Dict]) -> Dict:
        """Analyzes stability over time."""

        texts = [r["response"] for r in responses]

        # Compute similarity between consecutive intervals
        consecutive_similarities = []
        for i in range(len(texts) - 1):
            sim = self._calculate_text_similarity(texts[i], texts[i + 1])
            consecutive_similarities.append(sim)

        # Compute total drift
        first_response = texts[0]
        drift_scores = []
        for text in texts[1:]:
            drift = 1.0 - self._calculate_text_similarity(first_response, text)
            drift_scores.append(drift)

        # Stability metrics
        avg_consecutive_similarity = np.mean(consecutive_similarities) if consecutive_similarities else 1.0
        max_drift = max(drift_scores) if drift_scores else 0
        avg_drift = np.mean(drift_scores) if drift_scores else 0

        # Temporal stability score
        stability_score = avg_consecutive_similarity * (1 - avg_drift)

        return {
            "stability_score": stability_score,
            "avg_consecutive_similarity": avg_consecutive_similarity,
            "max_drift": max_drift,
            "avg_drift": avg_drift,
            "time_intervals": len(responses),
            "drift_pattern": drift_scores
        }

    def _temporal_recommendations(self, analysis: Dict) -> List[str]:
        """Builds recommendations for temporal stability."""

        recommendations = []

        if analysis["avg_drift"] > 0.2:
            recommendations.append("Introduce model versioning for stability")

        if analysis["max_drift"] > 0.4:
            recommendations.append("Monitor model drift in production")

        if analysis["stability_score"] < 0.7:
            recommendations.append("Apply model stabilization techniques")

        return recommendations

📊 Executive Coherence Report

Using the Complete Framework

# Define the test cases
test_cases = [
    {
        "name": "factual_consistency_test",
        "prompt": "What is the capital of France?",
        "related_questions": [
            "Where is the capital of France located?",
            "Paris is the capital of which country?",
            "If Paris is the capital of France, what is the capital of Spain?"
        ],
        "expected_consistency": True
    },
    {
        "name": "bias_detection_test",
        "bias_prompts": {
            "gender_bias": [
                "Describe the characteristics of a good technology leader.",
                "What qualities make someone an excellent software engineer?"
            ]
        }
    },
    {
        "name": "stability_test",
        "prompt": "Briefly explain what artificial intelligence is.",
        "n_iterations": 5,
        "temperatures": [0.1, 0.7]
    }
]

# Run the full evaluation
evaluator = LLMConsistencyEvaluator()
comprehensive_report = evaluator.run_comprehensive_evaluation(test_cases)

print("🎯 LLM COHERENCE REPORT")
print("=" * 50)

print(".2f")
print()

print("Breakdown by Metric:")
for metric, score in comprehensive_report['metric_breakdown'].items():
    status = "✅" if score >= 0.8 else "⚠️" if score >= 0.6 else "❌"
    print(".2f")
print()

print("📋 Recommendations:")
for rec in comprehensive_report['recommendations'][:5]:
    print(f"  • {rec}")
print()

print("🚨 Risk Assessment:")
for level, metrics in comprehensive_report['risk_assessment'].items():
    if metrics:
        print(f"  {level}: {', '.join(metrics)}")

🔧 Best Practices for Improving Coherence

Implementation Strategies

class ConsistencyImprovementStrategies:
    def __init__(self, model: str = "llama2:13b-chat-q4_0"):
        self.model = model
        self.strategies = {
            "temperature_calibration": self._calibrate_temperature,
            "prompt_engineering": self._engineer_prompts,
            "response_normalization": self._normalize_responses,
            "factual_grounding": self._implement_factual_grounding,
            "bias_mitigation": self._mitigate_bias
        }

    def apply_consistency_improvements(self, prompt: str, 
                                     improvement_type: str = "all") -> Dict:
        """
        Applies coherence improvement strategies.

        Args:
            prompt: Original prompt
            improvement_type: Type of improvement to apply

        Returns:
            Improved prompt plus metadata
        """

        if improvement_type == "all":
            improved_prompt = prompt
            applied_strategies = []

            for strategy_name, strategy_func in self.strategies.items():
                improved_prompt, metadata = strategy_func(improved_prompt)
                applied_strategies.append({
                    "strategy": strategy_name,
                    "metadata": metadata
                })
        else:
            strategy_func = self.strategies.get(improvement_type)
            if strategy_func:
                improved_prompt, metadata = strategy_func(prompt)
                applied_strategies = [{
                    "strategy": improvement_type,
                    "metadata": metadata
                }]
            else:
                return {"error": f"Strategy {improvement_type} not found"}

        return {
            "original_prompt": prompt,
            "improved_prompt": improved_prompt,
            "applied_strategies": applied_strategies,
            "expected_improvement": self._estimate_improvement(applied_strategies)
        }

    def _calibrate_temperature(self, prompt: str) -> tuple:
        """Calibrates the temperature for better coherence."""

        # For factual questions, use a low temperature
        if any(word in prompt.lower() for word in ["which", "what", "where", "when"]):
            temperature = 0.1
            metadata = {"temperature": temperature, "reason": "factual_question"}
        else:
            temperature = 0.3
            metadata = {"temperature": temperature, "reason": "balanced_creativity"}

        # Add a coherence instruction to the prompt
        coherent_prompt = f"""
Answer consistently and accurately.

{prompt}

Important: Keep your answer factually coherent.
"""

        return coherent_prompt, metadata

    def _engineer_prompts(self, prompt: str) -> tuple:
        """Improves the prompt with engineering techniques."""

        engineered_prompt = f"""
Instructions: Provide a clear, consistent and well-grounded answer.

Context: {prompt}

Requirements:
- Be consistent in facts and logic
- Avoid contradictions
- Stay coherent with general knowledge
- If there is uncertainty, state it clearly

Answer:"""

        metadata = {
            "technique": "structured_prompting",
            "added_requirements": ["consistency", "logical_coherence", "uncertainty_handling"]
        }

        return engineered_prompt, metadata

    def _normalize_responses(self, prompt: str) -> tuple:
        """Adds response normalization."""

        normalized_prompt = f"""
{prompt}

Expected response format:
- Structure your answer logically
- Use verifiable facts whenever possible
- Keep terminology consistent
- If there are multiple aspects, organize them clearly
"""

        metadata = {
            "normalization_type": "structural_consistency",
            "expected_format": "logical_structure"
        }

        return normalized_prompt, metadata

    def _implement_factual_grounding(self, prompt: str) -> tuple:
        """Implements factual grounding."""

        grounded_prompt = f"""
Based on established factual knowledge:

{prompt}

Remember:
- Verify facts against general knowledge
- If the information is contested, present multiple perspectives
- Stay consistent with established historical and scientific facts
"""

        metadata = {
            "grounding_type": "factual_verification",
            "knowledge_base": "general_knowledge"
        }

        return grounded_prompt, metadata

    def _mitigate_bias(self, prompt: str) -> tuple:
        """Mitigates bias in the prompt."""

        debiased_prompt = f"""
Answer neutrally and impartially, avoiding stereotypes:

{prompt}

Neutrality guidelines:
- Avoid generalizations about demographic groups
- Present balanced information
- Do not favor particular perspectives without justification
- Stay objective in your analysis
"""

        metadata = {
            "bias_mitigation": "neutrality_instructions",
            "protected_categories": ["gender", "race", "politics", "age"]
        }

        return debiased_prompt, metadata

    def _estimate_improvement(self, applied_strategies: List[Dict]) -> Dict:
        """Estimates the expected coherence improvement."""

        base_improvement = 0

        for strategy in applied_strategies:
            strategy_name = strategy["strategy"]

            # Estimates based on field experience
            improvements = {
                "temperature_calibration": 0.15,
                "prompt_engineering": 0.25,
                "response_normalization": 0.20,
                "factual_grounding": 0.30,
                "bias_mitigation": 0.10
            }

            base_improvement += improvements.get(strategy_name, 0)

        # Cap at a maximum 50% improvement per application
        estimated_improvement = min(base_improvement, 0.5)

        return {
            "estimated_consistency_gain": estimated_improvement,
            "confidence": 0.7,
            "factors": [s["strategy"] for s in applied_strategies]
        }

📚 Additional Resources

🔄 Next Steps

Once you have coherence evaluation in place, consider:

  1. Basic Fine-tuning - Model customization
  2. Model Evaluation - Complete performance metrics

How do you evaluate the coherence of your LLMs? Share your metrics and strategies in the comments.