The NL2FOL system uses a sophisticated multi-stage pipeline to convert natural language statements into first-order logic formulas. Each stage progressively refines the representation, ultimately producing a formal logical formula that can be verified by SMT solvers.
The pipeline is implemented in the NL2FOL class (src/nl_to_fol.py:15-388) and orchestrates multiple LLM calls with specialized prompts to extract logical structure from text.
# From src/nl_to_fol.py:71-83def extract_claim_and_implication(self): with open("prompts/prompt_nl_to_ci.txt", encoding="ascii", errors="ignore") as f: prompt = f.read() + self.sentence result = self.get_llm_result(prompt) for line in result.split("\n"): if 'Claim' in line: self.claim = line[line.find(':') + 1:] if 'Implication' in line: self.implication = line[line.find(':') + 1:]
Input: "I met a tall man who loved to eat cheese, now I believe all tall people like cheese"Output:Claim: "A tall man loves cheese."Implication: "All tall people like cheese."
The prompt instructs the LLM to:
Identify multiple claims if present, but only one implication
Remove subordinating conjunctions from implications
Replace pronouns with appropriate nouns for clarity
# From src/nl_to_fol.py:85-103def get_referring_expressions(self): with open("prompts/prompt_referring_expressions.txt", encoding="ascii", errors="ignore") as f: prompt = f.read() result_claim = self.get_llm_result(prompt + self.claim) for line in result_claim.split("\n"): if "Referring expressions" in line: self.claim_ref_exp = line[line.find(':') + 1:] break result_implication = self.get_llm_result(prompt + self.implication) for line in result_implication.split("\n"): if "Referring expressions" in line: self.implication_ref_exp = line[line.find(':') + 1:] break
Input: "A tall man loved cheese"Referring expressions: A tall man, cheeseInput: "The new math teacher is fat"Referring expressions: The new math teacherInput: "Giving 10% of your income to the Church will free a child's soul"Referring expressions: Your income, church, child's soul, limbo, heaven
# From src/nl_to_fol.py:260-280def get_entity_mapping(self): current_char = 'a' # Map equal entities to the same variable for (s1, s2) in self.equal_entities: if s1 in self.entity_mappings: self.entity_mappings[s2] = self.entity_mappings[s1] elif s2 in self.entity_mappings: self.entity_mappings[s1] = self.entity_mappings[s2] else: self.entity_mappings[s1] = current_char self.entity_mappings[s2] = current_char current_char = chr(ord(current_char) + 1) # Map remaining entities for s in self.claim_ref_exp.split(','): if s not in self.entity_mappings: self.entity_mappings[s] = current_char current_char = chr(ord(current_char) + 1)
The system ensures predicate arities are consistent using fix_inconsistent_arities():
# From src/helpers.py:61-104def fix_inconsistent_arities(clauses1, clauses2): predicates = {} # Extract predicates and their arities for clause in all_clauses: predicate = clause.split('(')[0] arity = len(clause.split(',')) if predicate in predicates: if predicates[predicate] != arity: predicates[predicate] = min(predicates[predicate], arity) else: predicates[predicate] = arity
If the same predicate appears with different numbers of arguments, the system uses the minimum arity to maintain consistency.
The prompt teaches the LLM to combine properties with logical operators:
Example 1:Input: "A tall man loves cheese"Properties: IsTall(x), LovesCheese(x)Logical Form: IsTall(x) & LovesCheese(x)Example 2:Input: "Smacking children stops bad behavior"Properties: IsSmacked(x), HasBadBehavior(x)Logical Form: IsSmacked(x) -> ~HasBadBehavior(x)
Input: "I met a tall man who loved to eat cheese, now I believe all tall people like cheese"Final FOL:exists x (IsTall(x) and LovesCheese(x)) -> forall y (IsTall(y) -> LovesCheese(y))
This formula reads: “If there exists a tall person who loves cheese, then all tall people love cheese” - which is clearly fallacious!