"""Unified Co-Pilot: 6 tabs over one Gradio Blocks. Top-level layout: Tabs: - "Multi-Head Demo" — original encoder demo (4 task heads, V3 nocompress + meanpool checkpoint, curated customer dropdown) - "Why Liquid" — architectural pitch (original demo content) - "Integration" — build-it-yourself guide (original demo content) - "Dispute Co-Pilot" — friendly-fraud classifier + attribution - "Collections Co-Pilot" — treatment-response scoreboard - "Fraud Co-Pilot" — pattern stage + type classifier Each tab's content is a composable `_build_..._tab_contents(...)` helper exported from the per-surface app module. Models are loaded once at startup (4 model instances total — V3 multi-head + 3 surface-specific multi-surface). CLI: python -m encoder.src.demo.copilot_app_unified \\ --multihead-checkpoint encoder/experiments/.../step_004999_slim.pt \\ --multihead-config encoder/configs/model_nocompress.yaml \\ --multihead-data-dir data/synthetic \\ --dispute-checkpoint encoder/experiments/dispute_legitimacy_v7/demo_checkpoint.pt \\ --collections-checkpoint encoder/experiments/collections_v3/demo_checkpoint.pt \\ --fraud-checkpoint encoder/experiments/fraud_pattern_v1/demo_checkpoint.pt \\ --cast-histories data/synthetic/cast_token_ids.npy \\ --port 7860 """ from __future__ import annotations import argparse from pathlib import Path import gradio as gr import torch # Original multi-head demo (V3) imports from src.data.schema import load_schema from src.demo.app import DemoData from src.demo.decode import TransactionDecoder from src.demo.merchant_catalog import DemoMerchantCatalog from encoder.src.demo.app import ( _build_cold_start_tab_contents, _build_demo_tab_contents, _build_integration_tab_contents, _build_why_liquid_tab_contents, _build_theme, _CSS, _HEADER_HTML, ) from encoder.src.demo.copilot_app import _build_tab as _build_dispute_tab from encoder.src.demo.copilot_app_collections import ( _build_tab as _build_collections_tab, ) from encoder.src.demo.copilot_app_fraud_pattern import ( _build_tab as _build_fraud_tab, ) from encoder.src.demo.copilot_inference import CopilotModel from encoder.src.demo.copilot_inference_collections import ( CollectionsCopilotModel, ) from encoder.src.demo.copilot_inference_fraud_pattern import ( FraudPatternCopilotModel, ) from encoder.src.demo.inference import EncoderDemoModel def build_unified_ui( multihead_model: EncoderDemoModel, multihead_data: DemoData, multihead_decoder: TransactionDecoder, multihead_merchant_catalog: DemoMerchantCatalog, dispute_model: CopilotModel, collections_model: CollectionsCopilotModel, fraud_model: FraudPatternCopilotModel, ) -> gr.Blocks: """Compose the original 3-tab demo + 3 new Co-Pilot tabs into one Blocks. Uses the original demo's theme + CSS so the visual vocabulary stays consistent across tabs. """ # Gradio 6.0 moved `css` and `theme` from Blocks() to launch(); the # caller passes them in via demo.queue().launch(theme=..., css=...). # We stash them on the returned Blocks instance for the caller. with gr.Blocks(title="Transaction Encoder — Liquid AI") as app: gr.HTML(_HEADER_HTML) with gr.Tabs(): with gr.Tab("Multi-Head Demo"): _build_demo_tab_contents( multihead_model, multihead_data, multihead_decoder, multihead_merchant_catalog, app, ) with gr.Tab("Cold Start"): _build_cold_start_tab_contents() with gr.Tab("Why Liquid"): _build_why_liquid_tab_contents() with gr.Tab("Integration"): _build_integration_tab_contents() with gr.Tab("Dispute Co-Pilot"): _build_dispute_tab(dispute_model) with gr.Tab("Collections Co-Pilot"): _build_collections_tab(collections_model) with gr.Tab("Fraud Co-Pilot"): _build_fraud_tab(fraud_model) return app def _load_multihead( checkpoint: Path, config: Path, schema: Path, data_dir: Path, dtype: torch.dtype, device: str, ) -> tuple[EncoderDemoModel, DemoData, TransactionDecoder, DemoMerchantCatalog]: """Load the original multi-head V3 model + curated test data.""" print("[multihead] schema + data ...") schema_cfg = load_schema(schema) data = DemoData(data_dir, schema_cfg) merchant_catalog = DemoMerchantCatalog(schema_cfg) decoder = TransactionDecoder(schema_cfg, merchant_catalog) print("[multihead] loading EncoderDemoModel ...") model = EncoderDemoModel( model_config_path=config, schema_path=schema, checkpoint_path=checkpoint, dtype=dtype, device=device, ) print(f"[multihead] checkpoint: {model.checkpoint_status}") return model, data, decoder, merchant_catalog def _load_copilots( dispute_checkpoint: Path, dispute_config: Path, collections_checkpoint: Path, collections_config: Path, fraud_checkpoint: Path, fraud_config: Path, schema: Path, cast_histories: Path, dispute_cast: Path, collections_cast: Path, fraud_cast: Path, device: torch.device, ) -> tuple[CopilotModel, CollectionsCopilotModel, FraudPatternCopilotModel]: """Load the three Co-Pilot surfaces. Each has its own backbone copy.""" print("[copilot 1/3] loading Dispute ...") dispute_model = CopilotModel.from_paths( checkpoint_path=dispute_checkpoint, model_config_path=dispute_config, schema_path=schema, histories_path=cast_histories, cast_path=dispute_cast, device=device, ) print("[copilot 2/3] loading Collections ...") collections_model = CollectionsCopilotModel.from_paths( checkpoint_path=collections_checkpoint, model_config_path=collections_config, schema_path=schema, histories_path=cast_histories, cast_path=collections_cast, device=device, ) print("[copilot 3/3] loading Fraud ...") fraud_model = FraudPatternCopilotModel.from_paths( checkpoint_path=fraud_checkpoint, model_config_path=fraud_config, schema_path=schema, histories_path=cast_histories, cast_path=fraud_cast, device=device, ) return dispute_model, collections_model, fraud_model def main() -> None: parser = argparse.ArgumentParser( description="Unified Transaction Encoder Gradio app (6 tabs)", ) # --- multi-head V3 --- parser.add_argument( "--multihead-checkpoint", type=Path, default=Path("encoder/experiments/nocompress_meanpool/" "encoder_sft_20260519_144916/checkpoints/step_004999_slim.pt"), ) parser.add_argument( "--multihead-config", type=Path, default=Path("encoder/configs/model_nocompress.yaml"), ) parser.add_argument( "--multihead-data-dir", type=Path, default=Path("data/synthetic"), ) # --- dispute --- parser.add_argument( "--dispute-checkpoint", type=Path, default=Path("encoder/experiments/dispute_legitimacy_v7/demo_checkpoint.pt"), ) parser.add_argument( "--dispute-config", type=Path, default=Path("encoder/configs/model_dispute_legitimacy.yaml"), ) parser.add_argument( "--dispute-cast", type=Path, default=Path("encoder/data/demo_cast.json"), ) # --- collections --- parser.add_argument( "--collections-checkpoint", type=Path, default=Path("encoder/experiments/collections_v3/demo_checkpoint.pt"), ) parser.add_argument( "--collections-config", type=Path, default=Path("encoder/configs/model_collections.yaml"), ) parser.add_argument( "--collections-cast", type=Path, default=Path("encoder/data/collections_cast.json"), ) # --- fraud --- parser.add_argument( "--fraud-checkpoint", type=Path, default=Path("encoder/experiments/fraud_pattern_v1/demo_checkpoint.pt"), ) parser.add_argument( "--fraud-config", type=Path, default=Path("encoder/configs/model_fraud_pattern.yaml"), ) parser.add_argument( "--fraud-cast", type=Path, default=Path("encoder/data/fraud_pattern_cast.json"), ) # --- shared --- parser.add_argument( "--schema", type=Path, default=Path("data/schema.yaml"), ) parser.add_argument( "--cast-histories", type=Path, default=Path("data/synthetic/token_ids.npy"), help="Histories file for the Co-Pilot tabs (subset of 18 cast customers).", ) parser.add_argument( "--device", type=str, default="cpu", choices=["cpu", "cuda", "mps"], ) parser.add_argument( "--dtype", type=str, default="float32", choices=["float32", "bfloat16"], ) parser.add_argument("--port", type=int, default=7860) parser.add_argument("--share", action="store_true") args = parser.parse_args() device = torch.device(args.device) multihead_dtype = ( torch.float32 if args.dtype == "float32" else torch.bfloat16 ) multihead_model, multihead_data, multihead_decoder, multihead_merchant = ( _load_multihead( checkpoint=args.multihead_checkpoint, config=args.multihead_config, schema=args.schema, data_dir=args.multihead_data_dir, dtype=multihead_dtype, device=args.device, ) ) dispute_model, collections_model, fraud_model = _load_copilots( dispute_checkpoint=args.dispute_checkpoint, dispute_config=args.dispute_config, collections_checkpoint=args.collections_checkpoint, collections_config=args.collections_config, fraud_checkpoint=args.fraud_checkpoint, fraud_config=args.fraud_config, schema=args.schema, cast_histories=args.cast_histories, dispute_cast=args.dispute_cast, collections_cast=args.collections_cast, fraud_cast=args.fraud_cast, device=device, ) print("all four models loaded.") demo = build_unified_ui( multihead_model=multihead_model, multihead_data=multihead_data, multihead_decoder=multihead_decoder, multihead_merchant_catalog=multihead_merchant, dispute_model=dispute_model, collections_model=collections_model, fraud_model=fraud_model, ) demo.queue().launch( server_name="0.0.0.0", server_port=args.port, share=args.share, theme=_build_theme(), css=_CSS, ) if __name__ == "__main__": main()