در پاسخ‌گوی متصل به پایگاه دانش، وجود چند سند همیشه پاسخ را روشن‌تر نمی‌کند؛ دو سند می‌توانند دربارهٔ یک ویژگی و نسخهٔ یکسان، مقدارهای متفاوتی داشته باشند. این درس یک دروازهٔ کوچک برای شواهد ساختاریافته می‌سازد: توافق را ثبت می‌کند و هنگام تعارض یا کمبود شاهد، مقدار پاسخ نمی‌دهد. همهٔ رکوردها تألیفی و ساختگی‌اند؛ بازیابی، استخراج متن، مدل زبانی، تماس و آزمایش سخت‌افزاری اجرا نمی‌شود.

۱. ادعا را به موضوع، ویژگی، مقدار و واحد تبدیل کنیم

تفکیک موضوع، ویژگی و واحد در رکورد شواهد ساختگی
تصویر مفهومی: تفکیک موضوع، ویژگی و واحد در رکورد شواهد ساختگی؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

در معرفی Deep Review در ۲۲ مهر ۱۴۰۵، Google Research بررسی بخش‌های سند و جمع‌بندی همراه با مراجعه به شواهد را جدا توضیح می‌دهد. این نوشتهٔ سازنده، زمینه‌ای برای توجه به بررسی ادعاهاست؛ مثال ما آن سامانه را بازسازی یا اجرا نمی‌کند. هیچ نتیجهٔ آزمایش یا عدد عملکرد آن نوشته را به کد حاضر نسبت نمی‌دهیم.

در نمونهٔ ما، «موضوع» نام برد فرضی board-A و «ویژگی» input_voltage است. مقدار باید عدد صحیح مثبت و واحد رشته‌ای صریح مانند mV باشد. عدد ۳۳۰۰ در اینجا اندازه‌گیری یا مشخصات برد واقعی نیست؛ فقط مقدار یک رکورد آموزشی است. متن طبیعی را به این فیلدها تبدیل نکرده‌ایم. اگر استخراج ویژگی از سند اشتباه باشد، گذشتن از این دروازه به تنهایی خطای استخراج را کشف نمی‌کند.

هر شاهد، شناسهٔ جداگانهٔ E و شناسهٔ منبع S دارد. فهرست S01 تا S04 صرفاً دفتر منابع فرضی است؛ وجود شناسه در آن، امضا، اصالت نویسنده یا اعتبار علمی سند را ثابت نمی‌کند. برای معماری گسترده‌تر، راهنمای RAG سازمانی را ببینید. تمرکز فعلی، یک قاعدهٔ تصمیم محدود روی رکوردهای ازپیش‌آماده است و کیفیت کل سامانهٔ پاسخ‌گویی را نمی‌سنجد.

۲. مرز نسخه را صریح بنویسیم تا تعارض ساختگی نسازیم

مرز نسخه‌های معتبر و جلوگیری از هم‌زمانی ناخواسته
تصویر مفهومی: مرز نسخه‌های معتبر و جلوگیری از هم‌زمانی ناخواسته؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

اعتبار E01 از نسخهٔ یک تا پیش از نسخهٔ سه است و مقدار ۳۳۰۰ میلی‌ولت دارد. E02 از نسخهٔ سه به بعد، مقدار ۵۰۰۰ را اعلام می‌کند. نسخه در این تمرین، عدد صحیح قراردادی است؛ تاریخ انتشار، نسخهٔ نرم‌افزار یا ساعت واقعی نیست. بازهٔ [since, until) ابتدا را شامل و انتها را خارج می‌کند؛ None یعنی حد بالای نسخه تعیین نشده است.

بنابراین در مرز نسخهٔ سه، E01 فعال نیست و E02 به تنهایی انتخاب می‌شود. تفاوت دو مقدار در دو بازهٔ جدا، در این قرارداد تعارض هم‌زمان نیست. حال E05 را اضافه می‌کنیم: از نسخهٔ دو تا پیش از چهار، برای همان موضوع و ویژگی ۳۳۰۰ می‌گوید. در نسخهٔ دو با E01 موافق است؛ در نسخهٔ سه با E02 اختلاف دارد. صرف نام جدیدتر یا ترتیب ورود رکورد، برنده تعیین نمی‌کند.

سیاست تألیفی ما این است که تمام شاهدهای فعالِ همان موضوع و ویژگی باید واحد و مقدار یکسان داشته باشند. نبود شاهد فعال، اختلاف مقدار یا ناسازگاری واحد، پاسخ بدون مقدار می‌سازد. رأی بیشتر هم تعارض را حل نمی‌کند؛ چند رکورد ممکن است از یک متن مشترک آمده باشند. مرجع تصمیم و رابطهٔ نسخه‌ها در کاربرد واقعی باید جدا تعیین شوند؛ درس چرخهٔ به‌روزرسانی پایگاه دانش این مسئولیت گسترده‌تر را توضیح می‌دهد.

۳. کد کامل پایتون و نگه داشتن منشأ تصمیم

تعارض شواهد و نگه داشتن هر دو منشأ هنگام خودداری از پاسخ
تصویر مفهومی: تعارض شواهد و نگه داشتن هر دو منشأ هنگام خودداری از پاسخ؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

فایل rag_evidence_conflicts.py را ذخیره و اجرا کنید. فقط کتابخانهٔ استاندارد نیاز است و اجرای ثبت‌شده با پایتون ۳٫۱۴٫۵ انجام شده است. کلاس Evidence، قالب رکورد را نگه می‌دارد. طبق مستندات dataclasses، بررسی‌های پس از ساخت را می‌توان صریح نوشت؛ راهنمای نوع به تنهایی مقدار، واحد یا منبع را اعتبارسنجی نمی‌کند.

"""Original offline evidence gate; no retrieval, language model or factual verification."""
from dataclasses import asdict, dataclass
import json
import platform

SOURCE_IDS = frozenset(('S01', 'S02', 'S03', 'S04'))  # Authored fictional source registry.


def token(value, field):
    if type(value) is not str or not value or any(ch.isspace() for ch in value):
        raise ValueError(field + ' must be a nonempty exact string without whitespace')


def positive_int(value, field):
    if type(value) is not int or value < 1:
        raise ValueError(field + ' must be an exact positive Python int')


@dataclass(frozen=True)
class Evidence:
    evidence_id: str
    source_id: str
    subject: str
    attribute: str
    value: int
    unit: str
    since_version: int
    until_version: int | None = None

    def __post_init__(self):
        for field in ('evidence_id', 'source_id', 'subject', 'attribute', 'unit'):
            token(getattr(self, field), field)
        if self.source_id not in SOURCE_IDS:
            raise ValueError('Unknown source ID; this registry is not authentication')
        positive_int(self.value, 'value')
        positive_int(self.since_version, 'since_version')
        if self.until_version is not None:
            positive_int(self.until_version, 'until_version')
            if self.until_version <= self.since_version:
                raise ValueError('The half-open version interval must be nonempty')


BASE = (
    Evidence('E01', 'S01', 'board-A', 'input_voltage', 3300, 'mV', 1, 3),
    Evidence('E02', 'S02', 'board-A', 'input_voltage', 5000, 'mV', 3),
    Evidence('E03', 'S03', 'board-B', 'input_voltage', 3300, 'mV', 1),
    Evidence('E04', 'S04', 'board-A', 'current_limit', 120, 'mA', 1),
)
OVERLAP = Evidence('E05', 'S03', 'board-A', 'input_voltage', 3300, 'mV', 2, 4)


def assess(rows, *, subject='board-A', attribute='input_voltage', version=3, unit='mV'):
    for field, value in (('subject', subject), ('attribute', attribute), ('unit', unit)):
        token(value, field)
    positive_int(version, 'version')
    if type(rows) not in (tuple, list) or any(type(row) is not Evidence for row in rows):
        raise ValueError('A list or tuple of validated Evidence objects is required')
    if len({row.evidence_id for row in rows}) != len(rows):
        raise ValueError('Duplicate evidence IDs are rejected, not overwritten')
    active = [row for row in rows if row.subject == subject and row.attribute == attribute
              and row.since_version <= version
              and (row.until_version is None or version < row.until_version)]
    decision = {'status': 'abstain', 'reason': 'no_active_evidence', 'value': None,
                'subject': subject, 'attribute': attribute, 'version': version, 'unit': unit,
                'evidence_ids': sorted(row.evidence_id for row in active),
                'source_ids': sorted({row.source_id for row in active})}
    if not active:
        return decision
    if any(row.unit != unit for row in active):
        return {**decision, 'reason': 'unit_mismatch'}
    values = {row.value for row in active}
    if len(values) != 1:
        return {**decision, 'reason': 'conflicting_values'}
    return {**decision, 'status': 'supported', 'reason': 'one_declared_value',
            'value': next(iter(values))}


def rejects(call):
    try:
        call()
    except ValueError:
        return True
    return False


def run_example():
    before = tuple(asdict(row) for row in BASE)
    older = assess(BASE, version=1)
    boundary = assess(BASE, version=3)
    with_overlap = BASE + (OVERLAP,)
    conflict = assess(with_overlap, version=3)
    agreement = assess(with_overlap, version=2)
    extra_vote = Evidence('E06', 'S04', 'board-A', 'input_voltage', 5000, 'mV', 3)
    other_unit = Evidence('E07', 'S04', 'board-A', 'input_voltage', 5, 'V', 3)
    unit_mismatch = assess(BASE + (other_unit,), version=3)
    missing = assess(BASE, attribute='temperature')
    checks = {
        'old_version_has_hand_reference_value_and_provenance': older['status'] == 'supported' and older['value'] == 3300 and older['evidence_ids'] == ['E01'] and older['source_ids'] == ['S01'],
        'half_open_boundary_selects_only_new_revision': boundary['value'] == 5000 and boundary['evidence_ids'] == ['E02'] and boundary['source_ids'] == ['S02'],
        'old_interval_includes_version_two': assess(BASE, version=2)['value'] == 3300 and assess(BASE, version=2)['evidence_ids'] == ['E01'],
        'conflict_abstains_and_retains_both_sides': conflict['status'] == 'abstain' and conflict['reason'] == 'conflicting_values' and conflict['value'] is None and conflict['evidence_ids'] == ['E02', 'E05'] and conflict['source_ids'] == ['S02', 'S03'],
        'equal_values_keep_all_supporting_provenance': agreement['status'] == 'supported' and agreement['value'] == 3300 and agreement['evidence_ids'] == ['E01', 'E05'],
        'extra_same_value_vote_does_not_resolve_conflict': assess(with_overlap + (extra_vote,))['reason'] == 'conflicting_values' and assess(with_overlap + (extra_vote,))['value'] is None,
        'uncovered_attribute_abstains_without_citations': missing['reason'] == 'no_active_evidence' and missing['value'] is None and missing['evidence_ids'] == [],
        'future_evidence_is_not_used_early': assess((BASE[1],), version=2)['reason'] == 'no_active_evidence',
        'expired_evidence_is_not_used_at_exclusive_end': assess((BASE[0],), version=3)['reason'] == 'no_active_evidence',
        'unit_mismatch_abstains_instead_of_implicit_conversion': unit_mismatch['reason'] == 'unit_mismatch' and unit_mismatch['value'] is None and unit_mismatch['evidence_ids'] == ['E02', 'E07'],
        'input_order_does_not_change_decision_or_provenance_order': assess(tuple(reversed(with_overlap))) == conflict,
        'unknown_source_id_rejected': rejects(lambda: Evidence('E99', 'UNKNOWN', 'board-A', 'input_voltage', 3300, 'mV', 1)),
        'duplicate_evidence_id_rejected': rejects(lambda: assess(BASE + (BASE[0],))),
        'invalid_or_empty_version_intervals_rejected': all((rejects(lambda: Evidence('E90', 'S01', 'board-A', 'input_voltage', 3300, 'mV', 0)), rejects(lambda: Evidence('E90', 'S01', 'board-A', 'input_voltage', 3300, 'mV', 3, 3)), rejects(lambda: Evidence('E90', 'S01', 'board-A', 'input_voltage', 3300, 'mV', 1, True)))),
        'bad_values_identifiers_and_record_containers_rejected': all((rejects(lambda: Evidence('', 'S01', 'board-A', 'input_voltage', 3300, 'mV', 1)), rejects(lambda: Evidence('E90', 'S01', 'board-A', 'input_voltage', True, 'mV', 1)), rejects(lambda: Evidence('E90', 'S01', 'board-A', 'input_voltage', 3.3, 'V', 1)), rejects(lambda: assess('E01')), rejects(lambda: assess([{}])))),
        'invalid_query_version_and_fields_rejected': all((rejects(lambda: assess(BASE, version=True)), rejects(lambda: assess(BASE, version=0)), rejects(lambda: assess(BASE, unit='')), rejects(lambda: assess(BASE, subject=1)))),
        'other_subject_and_attribute_are_not_mixed': assess(BASE, subject='board-B')['value'] == 3300 and assess(BASE, subject='board-B')['evidence_ids'] == ['E03'] and assess(BASE, attribute='current_limit', unit='mA')['value'] == 120 and assess(BASE, attribute='current_limit', unit='mA')['evidence_ids'] == ['E04'],
        'empty_batch_abstains_and_input_records_unchanged': assess(())['reason'] == 'no_active_evidence' and tuple(asdict(row) for row in BASE) == before,
    }
    assert len(checks) == 18 and all(value is True for value in checks.values()), checks
    return {'python': platform.python_version(), 'scope': 'authored_structured_evidence_gate_only',
            'source_registry': sorted(SOURCE_IDS),
            'interval_policy': 'positive integer versions; since <= version < until, or no upper bound',
            'decision_policy': 'exact declared key and unit; all active values must agree; no majority or freshness score',
            'old_version': older, 'half_open_boundary': boundary,
            'overlap_agreement': agreement, 'overlap_conflict': conflict,
            'unit_mismatch': unit_mismatch, 'uncovered_attribute': missing,
            'retrieval_llm_truth_auth_or_physical_measurement_executed': False, 'checks': checks}


if __name__ == '__main__':
    print(json.dumps(run_example(), ensure_ascii=False, indent=2))

شناسهٔ منبع ناشناخته، شناسهٔ شاهد تکراری، بازهٔ تهی و نوع نامجاز استثنا می‌دهند؛ این‌ها خرابی قرارداد ورودی‌اند و با «شاهد کافی نیست» یکی نیستند. برای مثال، bool با اینکه در برخی عملیات عددی شبیه صفر و یک رفتار می‌کند، مقدار یا نسخهٔ معتبر این کلاس نیست. حالت frozen جلوی انتساب عادی به فیلد را می‌گیرد، اما سامانهٔ امنیتی یا تضمین حقیقت داده ایجاد نمی‌کند.

یادداشت مرور PROV از W3C اطلاعات منشأ را برای بررسی چگونگی تولید داده و ارزیابی اعتماد مطرح می‌کند. در خروجی ما، شناسه‌های شاهد و منبع کنار تصمیم می‌مانند؛ حتی در اختلاف، هیچ‌کدام از دو طرف پنهان نمی‌شود. این دفتر ساده، پیاده‌سازی RDF، اعتبارسنج استاندارد PROV یا اثبات اصالت منبع نیست. نام و نسخهٔ واقعی سند باید در لایهٔ دیگری به این شناسه‌ها متصل شوند.

۴. توافق، تعارض و نبود پوشش را در خروجی جدا بخوانیم

آزمون آفلاین قواعد تصمیم و دفتر ثبت شواهد
تصویر مفهومی: آزمون آفلاین قواعد تصمیم و دفتر ثبت شواهد؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

در نسخهٔ یک، مقدار ۳۳۰۰ با شاهد E01 گزارش می‌شود؛ در مرز نسخهٔ سه، مقدار ۵۰۰۰ با E02. با افزودن E05، نتیجهٔ نسخهٔ دو همچنان ۳۳۰۰ است، ولی هر دو شاهد E01 و E05 ثبت می‌شوند. در نسخهٔ سه، وضعیت abstain با دلیل conflicting_values و مقدار None می‌آید؛ E02 و E05 در فهرست منشأ باقی می‌مانند. این تصمیم را می‌توان از جدول بازه‌ها مستقل از اجرای برنامه محاسبه کرد.

{
  "python": "3.14.5",
  "scope": "authored_structured_evidence_gate_only",
  "source_registry": [
    "S01",
    "S02",
    "S03",
    "S04"
  ],
  "interval_policy": "positive integer versions; since <= version < until, or no upper bound",
  "decision_policy": "exact declared key and unit; all active values must agree; no majority or freshness score",
  "old_version": {
    "status": "supported",
    "reason": "one_declared_value",
    "value": 3300,
    "subject": "board-A",
    "attribute": "input_voltage",
    "version": 1,
    "unit": "mV",
    "evidence_ids": [
      "E01"
    ],
    "source_ids": [
      "S01"
    ]
  },
  "half_open_boundary": {
    "status": "supported",
    "reason": "one_declared_value",
    "value": 5000,
    "subject": "board-A",
    "attribute": "input_voltage",
    "version": 3,
    "unit": "mV",
    "evidence_ids": [
      "E02"
    ],
    "source_ids": [
      "S02"
    ]
  },
  "overlap_agreement": {
    "status": "supported",
    "reason": "one_declared_value",
    "value": 3300,
    "subject": "board-A",
    "attribute": "input_voltage",
    "version": 2,
    "unit": "mV",
    "evidence_ids": [
      "E01",
      "E05"
    ],
    "source_ids": [
      "S01",
      "S03"
    ]
  },
  "overlap_conflict": {
    "status": "abstain",
    "reason": "conflicting_values",
    "value": null,
    "subject": "board-A",
    "attribute": "input_voltage",
    "version": 3,
    "unit": "mV",
    "evidence_ids": [
      "E02",
      "E05"
    ],
    "source_ids": [
      "S02",
      "S03"
    ]
  },
  "unit_mismatch": {
    "status": "abstain",
    "reason": "unit_mismatch",
    "value": null,
    "subject": "board-A",
    "attribute": "input_voltage",
    "version": 3,
    "unit": "mV",
    "evidence_ids": [
      "E02",
      "E07"
    ],
    "source_ids": [
      "S02",
      "S04"
    ]
  },
  "uncovered_attribute": {
    "status": "abstain",
    "reason": "no_active_evidence",
    "value": null,
    "subject": "board-A",
    "attribute": "temperature",
    "version": 3,
    "unit": "mV",
    "evidence_ids": [],
    "source_ids": []
  },
  "retrieval_llm_truth_auth_or_physical_measurement_executed": false,
  "checks": {
    "old_version_has_hand_reference_value_and_provenance": true,
    "half_open_boundary_selects_only_new_revision": true,
    "old_interval_includes_version_two": true,
    "conflict_abstains_and_retains_both_sides": true,
    "equal_values_keep_all_supporting_provenance": true,
    "extra_same_value_vote_does_not_resolve_conflict": true,
    "uncovered_attribute_abstains_without_citations": true,
    "future_evidence_is_not_used_early": true,
    "expired_evidence_is_not_used_at_exclusive_end": true,
    "unit_mismatch_abstains_instead_of_implicit_conversion": true,
    "input_order_does_not_change_decision_or_provenance_order": true,
    "unknown_source_id_rejected": true,
    "duplicate_evidence_id_rejected": true,
    "invalid_or_empty_version_intervals_rejected": true,
    "bad_values_identifiers_and_record_containers_rejected": true,
    "invalid_query_version_and_fields_rejected": true,
    "other_subject_and_attribute_are_not_mixed": true,
    "empty_batch_abstains_and_input_records_unchanged": true
  }
}

نام supported فقط یعنی طبق قواعد نوشته‌شده، یک مقدار اعلام‌شدهٔ مشترک پیدا شده است. دو سند موافق ممکن است هر دو اشتباه باشند؛ فهرست منابع هم استقلال آن‌ها را نشان نمی‌دهد. تابع، اعتبار محتوایی یا اعتمادپذیری گوینده را نمره نمی‌دهد و جملهٔ طبیعی تولید نمی‌کند. پرسش دربارهٔ ویژگیِ بدون شاهد، no_active_evidence می‌گیرد؛ درخواست برای برد دیگر یا ویژگی جریان، با مقدار ولتاژ برد نخست مخلوط نمی‌شود.

در نمونهٔ واحد، یک رکورد ۵ ولت و دیگری ۵۰۰۰ میلی‌ولت دارند. این دو لزوماً از نظر فیزیکی متناقض نیستند؛ اما تبدیل واحد در برنامه تعریف نشده است، پس unit_mismatch می‌گیریم. ناسازگاری قرارداد را به اشتباه علمی سند تبدیل نمی‌کنیم. هجده بررسی، این مرزها، ترتیب مستقل خروجی، رد ورودی خراب و حفظ رکوردهای اولیه را می‌آزمایند؛ موفقیت آن‌ها نرخ خطای مدل، پوشش اسناد واقعی یا ظرفیت تلفن هوشمند را نشان نمی‌دهد.

۵. چهار تمرین پاسخ‌دار و مسیر رسیدگی به تعارض

تفاوت پشتیبانی قراردادی از پاسخ با اثبات واقعیت
تصویر مفهومی: تفاوت پشتیبانی قراردادی از پاسخ با اثبات واقعیت؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

تمرین اول: چرا در نسخهٔ سه، مقدار قدیمیِ E01 با E02 تعارض ندارد؟ پاسخ: انتهای بازهٔ E01 خارج از بازه است؛ در همان مرز، فقط E02 فعال می‌ماند. E05 به دلیل بازهٔ متفاوت هنوز فعال است و تعارض نسخهٔ سه را ایجاد می‌کند.

تمرین دوم: اگر یک شاهد دیگر با مقدار ۵۰۰۰ اضافه کنیم، آیا تعارض حل می‌شود؟ پاسخ: خیر؛ سیاست ما رأی‌گیری نیست و وجود مقدار فعال ۳۳۰۰ همچنان اختلاف می‌سازد. تعداد رکورد بیشتر هم به تنهایی استقلال منابع یا درستی مقدار را ثابت نمی‌کند.

تمرین سوم: آیا ثبت S02 در خروجی، اصالت آن سند را تأیید می‌کند؟ پاسخ: خیر؛ فقط مسیر پیگیری رکورد را نگه می‌داریم. هویت، مجوز دسترسی، متن کامل و اعتبار مرجع داخل این برنامه بررسی نمی‌شوند.

تمرین چهارم: برای اختلاف واحد چه اقدامی مجاز است؟ پاسخ: ابتدا قرارداد تبدیل و تطبیق ویژگی را مستقل تعریف و آزمون می‌کنیم؛ برنامهٔ فعلی مقدار حدسی نمی‌دهد. سپس بازبین می‌تواند منشأها را بررسی و رکورد اصلاح‌شده را دوباره وارد آزمون کند. abstain فقط وضعیت داده است و خودکار تماس را منتقل نمی‌کند. منابع در ۲۴ مهر ۱۴۰۵ خوانده شده‌اند؛ زمینهٔ خبری جدید با اجرای مدل یا بازتولید پژوهش یکسان نیست.

منابع و مطالعهٔ بیشتر

بررسی منابع: ۲۴ مهر ۱۴۰۵. تحلیل‌ها و پیشنهادهای اجرایی، جمع‌بندی نویسنده‌اند.