knowledge/fact_validation.cpp¶
Post-generation fact checking implementation. More...
Namespaces¶
| Name |
|---|
| sgns |
| sgns::neoswarm |
| sgns::neoswarm::knowledge |
Detailed Description¶
Post-generation fact checking implementation.
Date: 2026-05-06
Source code¶
#include "fact_validation.hpp"
#include "common/logging.hpp"
#include <algorithm>
#include <cmath>
#include <regex>
#include <sstream>
namespace sgns::neoswarm::knowledge
{
namespace
{
auto ValidationLogger()
{
return neoswarm::CreateLogger( "FactValidation" );
}
} // namespace
FactValidation::FactValidation( std::shared_ptr<KnowledgeRetrieval> retrieval )
: retrieval_( std::move( retrieval ) )
{
}
bool FactValidation::IsAvailable() const
{
return retrieval_ && retrieval_->IsLoaded();
}
// -----------------------------------------------------------------------
// ExtractNumericClaims
// -----------------------------------------------------------------------
std::vector<std::pair<std::string, double>> FactValidation::ExtractNumericClaims( const std::string& text ) const
{
std::vector<std::pair<std::string, double>> claims;
static const std::regex kNumPattern( R"((?:is|=|equals?|approximately|about|around)\s+([\d,]+(?:\.\d+)?))" );
std::sregex_iterator it( text.begin(), text.end(), kNumPattern );
std::sregex_iterator end;
for ( ; it != end; ++it )
{
std::string num_str = ( *it )[1].str();
num_str.erase( std::remove( num_str.begin(), num_str.end(), ',' ), num_str.end() );
try
{
double val = std::stod( num_str );
claims.push_back( { ( *it )[0].str(), val } );
}
catch ( ... )
{
}
}
return claims;
}
// -----------------------------------------------------------------------
// Contradicts
// -----------------------------------------------------------------------
bool FactValidation::Contradicts( double claim, const std::string& fact_content ) const
{
static const std::regex kNumPattern( R"([\d,]+(?:\.\d+)?)" );
std::sregex_iterator it( fact_content.begin(), fact_content.end(), kNumPattern );
std::sregex_iterator end;
for ( ; it != end; ++it )
{
std::string num_str = it->str();
num_str.erase( std::remove( num_str.begin(), num_str.end(), ',' ), num_str.end() );
try
{
double fact_val = std::stod( num_str );
if ( fact_val == 0.0 )
{
continue;
}
double rel_diff = std::abs( claim - fact_val ) / std::abs( fact_val );
if ( rel_diff > 0.01 )
{
return true; // >1% difference = contradiction
}
}
catch ( ... )
{
}
}
return false;
}
// -----------------------------------------------------------------------
// Validate
// -----------------------------------------------------------------------
FactValidation::ValidationResult FactValidation::Validate( const std::string& output,
const std::vector<KnowledgeFact>& grounding_facts ) const
{
ValidationResult result;
if ( !IsAvailable() || grounding_facts.empty() )
{
ValidationLogger()->debug( "FactValidation: skipping (unavailable or no grounding facts)" );
return result;
}
auto claims = ExtractNumericClaims( output );
if ( claims.empty() )
{
return result;
}
int contradiction_count = 0;
for ( const auto& [claim_text, claim_val] : claims )
{
for ( const auto& fact : grounding_facts )
{
if ( Contradicts( claim_val, fact.m_content ) )
{
result.m_contradictions.push_back( "Claim '" + claim_text + "' may contradict: " + fact.m_content );
++contradiction_count;
}
}
}
if ( contradiction_count > 0 )
{
result.passed_ = false;
result.m_contradictionScore =
std::min( static_cast<float>( contradiction_count ) / static_cast<float>( claims.size() ), 1.0f );
result.suggestion_ = "Output contains " + std::to_string( contradiction_count ) +
" potential contradiction(s) with Grokipedia facts.";
ValidationLogger()->warn( "FactValidation: {} contradiction(s) detected", contradiction_count );
}
return result;
}
} // namespace sgns::neoswarm::knowledge
Updated on 2026-07-25 at 22:56:57 +0000