Views
No views yet
qa/v4_generation_tracking.md for exactly how this dataset was built.{"role": "user", "content": "Who are you?"} (the default
HF "Use this model" snippet above) will just get you a generic base-Qwen answer — the
fine-tuning has nothing to activate on without a schema.1from transformers import AutoTokenizer, AutoModelForCausalLM
2
3tokenizer = AutoTokenizer.from_pretrained("BeastxD/text2cypher_lora_v4_balanced")
4model = AutoModelForCausalLM.from_pretrained("BeastxD/text2cypher_lora_v4_balanced", device_map="auto")
5
6SYSTEM_PROMPT_TEMPLATE = (
7 """You are a Cypher query generation assistant for a Neo4j graph database.
8
9You are given a graph schema and a question in natural language. Use the
10schema strictly - it is the only source of truth for what exists in the graph.
11
12How to read the schema:
13- 'Node properties' lists each node label together with its properties and
14 their types (e.g. STRING, FLOAT, DATE, POINT). Some properties list
15 example or available values - these show the kind of data to expect, not
16 an exhaustive list to match against literally unless the question refers
17 to one of them directly.
18- 'The relationships' lists every valid pattern of how node labels connect,
19 in the form (:LabelA)-[:REL_TYPE]->(:LabelB). This tells you both the
20 relationship type name and its direction - respect the direction when you
21 build your MATCH pattern.
22
23How to map the question to the schema:
241. Find the node label(s) the question is really asking about (the subject
25 and the target of the question).
262. Find the relationship path in the schema that connects those labels -
27 questions often require traversing more than one relationship.
283. Identify any filters mentioned in the question (names, dates, categories,
29 thresholds) and match them to the correct property on the correct label.
304. If the question asks for a count, total, average, minimum, maximum, or
31 'top N', use the appropriate aggregation function and ORDER BY / LIMIT.
32
33Rules:
34- Use only labels, relationship types, and properties that literally appear
35 in the schema below. Never invent one.
36- Return ONLY the Cypher query - no explanation, no markdown fences, no
37 comments.
38- Return only the specific properties the question names. Return a whole
39 node only when the question asks generally about an entity without naming
40 particular attributes.
41- When computing a single overall aggregate (an overall average, count, or
42 sum), do not carry unrelated variables into the WITH that produces it -
43 every non-aggregated variable in a WITH implicitly groups the aggregate by
44 that variable, turning one intended overall result into one result per
45 group.
46- Before returning the query, check every relationship pattern you used against
47 the schema's relationship list. Your arrow direction and label order must
48 match one of the listed (:LabelA)-[:REL_TYPE]->(:LabelB) patterns exactly -
49 if your pattern is the reverse of a listed one, you have the direction
50 wrong and must flip it.
51- For "highest", "lowest", "top N", "most/least" phrasing, select with
52 ORDER BY <property> ASC|DESC LIMIT N rather than computing min()/max() and
53 re-matching on equality - re-matching on equality returns every tied row
54 instead of one deterministic answer.
55- If a MATCH path can reach the same return value multiple times through
56 multi-hop or branching traversal, use DISTINCT on it - unless the question
57 specifically asks for a count or list per relationship/edge, in which case
58 duplicates are the correct answer and DISTINCT must not be used.
59- When the question asks about a status, state, count threshold, or yes/no
60 condition ("accepted", "active", "at least one", "any", "some", "is X"),
61 first check whether the relevant node has a property in the schema that
62 directly represents that condition (a BOOLEAN, or a COUNT/INTEGER property
63 already tracking it) and filter on it directly. Do not reconstruct the
64 condition via a traversal or exists() check if a direct property already
65 encodes it.
66- If the property the question refers to (e.g. "type", "kind", "category")
67 does not exist on the node you first match, do not traverse further away
68 from it searching for a substitute property on a different node. Stay on
69 the matched node and use its closest literal property (e.g. count distinct
70 values of an existing identifying property on that same node) rather than
71 inventing a multi-hop path to a loosely related property elsewhere.
72- Return ONLY the Cypher query - no explanation, no markdown fences, no
73 comments.\n\nSchema:\n{schema}"""
74)
75
76schema = """Nodes:
77 Common properties:
78 · id:STRING — Stable canonical entity identifier
79 · name:STRING — Use FTS index (QUERY_FTS_INDEX) for fuzzy name lookups; CONTAINS as fallback
80 · first_observed:DATE — Native DATE. Compare with DATE literals: WHERE n.first_observed >= DATE('2024-01-01')
81 · last_observed:DATE — Native DATE. Use with first_observed for "active at date" checks
82 · status:STRING — ACTIVE / ARCHIVED / UNCERTAIN
83
84 Per-label descriptions and domain properties:
85 (:Customer) — a customer who owns appliances and submits work orders
86 · phone:STRING — primary contact phone number
87 · preferred_contact_method:STRING — [Phone, Email, SMS]
88 (:Appliance) — a specific appliance unit owned by a customer
89 · appliance_type:STRING — [Refrigerator, Washer, Dryer, Dishwasher, Oven, HVAC]
90 · brand:STRING — manufacturer brand name
91 · model_number:STRING — manufacturer model number
92
93Relationships:
94 (:Customer)-[:OWNS]->(:Appliance) — customer owns the appliance"""
95
96question = "What brand and model number does the appliance owned by customer 'Jane Doe' have?"
97
98messages = [
99 {"role": "system", "content": SYSTEM_PROMPT_TEMPLATE.format(schema=schema)},
100 {"role": "user", "content": question},
101]
102inputs = tokenizer.apply_chat_template(
103 messages, add_generation_prompt=True, tokenize=True,
104 return_dict=True, return_tensors="pt",
105).to(model.device)
106
107outputs = model.generate(**inputs, max_new_tokens=250, do_sample=False)
108print(tokenizer.decode(outputs[0][inputs["input_ids"].shape[-1]:], skip_special_tokens=True))
109# -> MATCH (c:Customer {name: 'Jane Doe'})-[:OWNS]->(a:Appliance) RETURN a.brand, a.model_numberBeastxD/text2cypher_lora_v4_raw), not adding new rows — easy-bucket rows trimmed from 2,489 down to 997 (domain-proportional random sample, seed 42) to match the complex bucket's count (995), medium (260) left as-is.qa/v4/trimmed_easy_rows_dropped_for_balance.csv in the training repo.common/validate_and_build.py in the training repo.common/semantic_rescore.py, same methodology as v2/v3) has not been run against this checkpoint yet — check the training repo's v4/evals_balanced/ for results once available. This model exists specifically to test whether complexity balance (at the cost of ~40% less training data than the raw version) helps or hurts real accuracy — that comparison isn't settled until both get evaluated.unsloth/qwen3-4b-instruct-2507-unsloth-bnb-4bit, 4-bit + rank-16 LoRA, targeting all attention + MLP projections.v3/code/runpod/docuprism_lora_training_runpod.ipynb), just repointed at the balanced v4 dataset — see v4/code/runpod/docuprism_lora_training_runpod_balanced.ipynb.