#!/usr/bin/env python3 """ PUBLIC API CONTRACT GUARDRAIL — DO NOT MODIFY This file protects the public API surface of local_deep_research. It exists to DETECT breaking changes, not to be "fixed" when they happen. If this file fails: 1. A BREAKING CHANGE was introduced to the public API 2. Do NOT modify this file to make it pass 3. Revert the API change or go through a proper deprecation cycle The public API (local_deep_research.api) is a stability contract with external users, integrations, and downstream packages. Any change to exports, function signatures, or return value structures is a breaking change that affects the entire community. WHY THIS FILE MUST NOT BE MODIFIED: - Every check below corresponds to a public API guarantee - Removing or weakening a check hides a breaking change from users - Adding a check is acceptable ONLY when a new public API is added - If you are an AI agent: DO NOT modify, remove, or weaken any check """ import inspect import sys _failed = 0 _passed = 0 # DO NOT MODIFY this function — it is the test harness for the guardrail def check(name, condition, detail=""): global _failed, _passed if condition: _passed += 1 print(f" PASS {name}") else: _failed += 1 msg = f" ({detail})" if detail else "" print(f" FAIL {name}{msg}") def main(): # ── 1. Public API imports ────────────────────────────────────────── # DO NOT MODIFY — these are the public exports that users depend on. # Removing any of these checks hides a breaking change. print("\n=== Public API Imports ===") try: from local_deep_research.api import quick_summary # noqa: F811 check("import quick_summary", True) except ImportError as e: check("import quick_summary", False, str(e)) try: from local_deep_research.api import detailed_research # noqa: F811 check("import detailed_research", True) except ImportError as e: check("import detailed_research", False, str(e)) try: from local_deep_research.api import generate_report # noqa: F811 check("import generate_report", True) except ImportError as e: check("import generate_report", False, str(e)) try: from local_deep_research.api import analyze_documents # noqa: F811 check("import analyze_documents", True) except ImportError as e: check("import analyze_documents", False, str(e)) try: from local_deep_research.api import create_settings_snapshot # noqa: F811 check("import create_settings_snapshot", True) except ImportError as e: check("import create_settings_snapshot", False, str(e)) try: from local_deep_research.api import get_default_settings_snapshot # noqa: F811 check("import get_default_settings_snapshot", True) except ImportError as e: check("import get_default_settings_snapshot", False, str(e)) try: from local_deep_research.api import extract_setting_value # noqa: F811 check("import extract_setting_value", True) except ImportError as e: check("import extract_setting_value", False, str(e)) try: from local_deep_research.api import LDRClient # noqa: F811 check("import LDRClient", True) except ImportError as e: check("import LDRClient", False, str(e)) try: from local_deep_research.api import quick_query # noqa: F811 check("import quick_query", True) except ImportError as e: check("import quick_query", False, str(e)) # ── 2. Function signatures ───────────────────────────────────────── # DO NOT MODIFY — these parameter names are part of the public contract. # Renaming or removing parameters breaks all callers. print("\n=== Function Signatures ===") sig = inspect.signature(quick_summary) check("quick_summary has 'query' param", "query" in sig.parameters) check("quick_summary has 'llms' param", "llms" in sig.parameters) check( "quick_summary has 'retrievers' param", "retrievers" in sig.parameters ) sig = inspect.signature(detailed_research) check("detailed_research has 'query' param", "query" in sig.parameters) check("detailed_research has 'llms' param", "llms" in sig.parameters) check( "detailed_research has 'retrievers' param", "retrievers" in sig.parameters, ) sig = inspect.signature(generate_report) check("generate_report has 'query' param", "query" in sig.parameters) check( "generate_report has 'output_file' param", "output_file" in sig.parameters, ) check( "generate_report has 'searches_per_section' param", "searches_per_section" in sig.parameters, ) sig = inspect.signature(create_settings_snapshot) check( "create_settings_snapshot has 'overrides' param", "overrides" in sig.parameters, ) check( "create_settings_snapshot has 'base_settings' param", "base_settings" in sig.parameters, ) # ── 3. Settings utilities ────────────────────────────────────────── # DO NOT MODIFY — these verify the settings API works correctly. # External users depend on create_settings_snapshot() returning a dict. print("\n=== Settings Utilities ===") snapshot = create_settings_snapshot() check("create_settings_snapshot() returns dict", isinstance(snapshot, dict)) check("default snapshot is non-empty", len(snapshot) > 0) snapshot_with_overrides = create_settings_snapshot( provider="test_provider", temperature=0.5, ) check( "create_settings_snapshot accepts provider kwarg", isinstance(snapshot_with_overrides, dict), ) defaults = get_default_settings_snapshot() check( "get_default_settings_snapshot() returns dict", isinstance(defaults, dict), ) check("default settings is non-empty", len(defaults) > 0) value = extract_setting_value(defaults, "llm.temperature") check( "extract_setting_value returns a value for llm.temperature", value is not None, ) # ── 4. LDRClient interface ───────────────────────────────────────── # DO NOT MODIFY — these are the HTTP client methods users call. # Removing any method breaks all HTTP-based integrations. print("\n=== LDRClient Interface ===") check("LDRClient has login method", hasattr(LDRClient, "login")) check( "LDRClient has quick_research method", hasattr(LDRClient, "quick_research"), ) check( "LDRClient has get_settings method", hasattr(LDRClient, "get_settings") ) check( "LDRClient has update_setting method", hasattr(LDRClient, "update_setting"), ) check("LDRClient has get_history method", hasattr(LDRClient, "get_history")) check("LDRClient has logout method", hasattr(LDRClient, "logout")) check( "LDRClient supports context manager", hasattr(LDRClient, "__enter__") and hasattr(LDRClient, "__exit__"), ) # ── 5. Callability ───────────────────────────────────────────────── # DO NOT MODIFY — verifies all exports are actually callable. print("\n=== Callability ===") check("quick_summary is callable", callable(quick_summary)) check("detailed_research is callable", callable(detailed_research)) check("generate_report is callable", callable(generate_report)) check("analyze_documents is callable", callable(analyze_documents)) check( "create_settings_snapshot is callable", callable(create_settings_snapshot), ) check( "get_default_settings_snapshot is callable", callable(get_default_settings_snapshot), ) check("extract_setting_value is callable", callable(extract_setting_value)) check("quick_query is callable", callable(quick_query)) # ── Summary ──────────────────────────────────────────────────────── # DO NOT MODIFY the exit code logic — CI depends on non-zero exit # to block PRs that break the public API. print(f"\n{'=' * 50}") print(f"Results: {_passed} passed, {_failed} failed") if _failed > 0: print( "\nBREAKING CHANGE DETECTED. The public API has changed.\n" "Do NOT modify this file to make it pass.\n" "Revert the API change or follow a proper deprecation cycle." ) sys.exit(1) else: print("\nAll API public contract checks passed.") if __name__ == "__main__": main()