File size: 3,386 Bytes
164e036
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
#!/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()