#!/usr/bin/env python3 """Call a self-hosted claim drafter and validate what comes back. Assumes vLLM is serving the exported adapter (see deploy/export_model.py): vllm serve Qwen/Qwen3.5-9B --lora-modules claim-drafter=./export/peft_adapter Then: python3 deploy/example_client.py python3 deploy/example_client.py --file my_disclosure.txt vLLM exposes an OpenAI-compatible API, so the standard client works against your own server — nothing leaves your infrastructure. """ import argparse import os import sys sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from claim_drafter.rewards import claim_reward, split_claims # Must stay byte-identical to the training system prompt. It was constant across # all 9,662 examples; changing it at inference is off-distribution. SYSTEM_PROMPT = ( "You are an expert US patent attorney. Draft a set of independent and " "dependent claims based on the provided invention description. Use " "correct USPTO formatting." ) EXAMPLE_DISCLOSURE = """Title: Adaptive Battery Thermal Management Technical Field and Background: Electric vehicle battery packs lose capacity when individual cells operate outside a narrow temperature band. Existing thermal management systems circulate coolant through the entire pack at a single flow rate, which wastes energy and still leaves interior modules measurably hotter than modules at the pack edge. Invention Disclosure: A controller reads temperature from sensors mounted on each battery module and drives an independent coolant valve for each module, holding every module inside a target temperature band rather than cooling the pack as a single unit. The controller adjusts each valve continuously from the per-module readings.""" def main(): ap = argparse.ArgumentParser() ap.add_argument("--base-url", default=os.environ.get("CLAIM_DRAFTER_URL", "http://localhost:8000/v1")) ap.add_argument("--model", default="claim-drafter") ap.add_argument("--file", help="path to a disclosure; uses a built-in example if omitted") ap.add_argument("--max-tokens", type=int, default=3072) args = ap.parse_args() disclosure = open(args.file).read() if args.file else EXAMPLE_DISCLOSURE from openai import OpenAI client = OpenAI(base_url=args.base_url, api_key="not-used") response = client.chat.completions.create( model=args.model, messages=[{"role": "system", "content": SYSTEM_PROMPT}, {"role": "user", "content": disclosure}], temperature=0.0, # drafting is not a creative task max_tokens=args.max_tokens, ) claims = response.choices[0].message.content print("=" * 70) print(claims) print("=" * 70) # The same function that provided the RL reward is the production guardrail. score = claim_reward(claims) n_claims = len(split_claims(claims)) print("\nformal validity: %.3f over %d claims" % (score, n_claims)) if score < 0.9: print("BELOW THRESHOLD — numbering or dependency defect.") print("Regenerate, or route to an attorney for review before showing a user.") sys.exit(1) print("Passed the formal checks.") print("Note: this verifies FORM, not novelty or patentability.") if __name__ == "__main__": main()