LLM
The integration of Large Language Models (LLM) into asplain allows users to generate natural language explanations for contrastive explanations. This feature enhances the interpretability of the results by providing human-readable insights. It also enables users to pose queries in natural language, which are then translated into corresponding query atoms for analysis.
Note
asplain only supports prompting LLM's over an API. The two API's that are available for now are google and openai. To access them you need to provide an API key. This can be done two different ways:
Using a .env file¶
OPENAI_API_KEY=<YOUR-OPENAI-API-KEY>
GEMINI_API_KEY=<YOUR-GOOGLE-API-KEY>
Directly in command-line¶
OPENAI_API_KEY=<YOUR-OPENAI-API-KEY> asplain [...] --llm=<LLM-TAG-OPENAI>
GEMINI_API_KEY=<YOUR-GOOGLE-API-KEY> asplain [...] --llm=<LLM-TAG-GOOGLE>
Labels
For the LLM to generate a better natural language explanations, the rules and atoms must have labels. This can be done by providing @label annotations in the ASP program. See the Tagging section for more details.
Usage¶
The natural language interaction can be done using the options --llm and --nl-query in the command line.
It is also integrated in the interactive explanation interface. After generating the contrastive explanation graph, the user can click on the "Generate Natural Language Explanation" button to generate a natural language explanation of the contrastive explanation.
Natural Language Explanation in James Bond Example
For details on the example see the James Bond example.
The following command generates a natural language explanation for the contrastive explanation of why Bond is not poisoned:
> asplain examples/james-bond/encoding.lp --nexplanations 0 -n 0 --model examples/james-bond/model.lp --cost-encoding src/asplain/encodings/costs/program-difference.lp --cost-encoding src/asplain/encodings/costs/model-difference.lp --llm GPT_4O --nl-query "Why is Bond not poisoned?"
Answer: 1
c a
Query (expected atoms): p
Foil model (satisfying query): p a t
Removed: c.
Added: t.
LLM Explanation:
To ensure Bond becomes poisoned, he needed to have contact with the toxin, which was added to the situation. Previously, his carefulness prevented him from being poisoned, as the constraints did not fire. By introducing the toxin, he gets poisoned regardless of taking the antidote or being careful.
SATISFIABLE
Models : 1
Calls : 1
Time : 4.350s (Solving: 4.05s 1st Model: 0.00s Unsat: 4.05s)
CPU Time : 0.459s
Prompt Templates¶
NL Query to Query Atoms¶
Your task is to translate a natural language question into an ASP query.
You will receive:
1 A natural language question.
2 A list of available atoms with labels.
The output must use following query syntax:
- A positive query is written as the atom itself:
Example: a
- A negative query is written with a leading minus sign:
Example: -b
- Multiple queries are separated by exactly one space:
Example: a -b c
Your task:
- Use a positive query: atom, when the question contains negation "not", "isn't", "wasn't", "doesn't", "didn't" or when user asks about a proposition that does not hold, is prevented, impossible, absent, or is desired to hold.
Typical question patterns:
Why not X?
Why isn't X?
Why is X not the case?
Why is X false?
Why is X absent?
Why can't X happen?
What prevents X?
Why does someone avoid X?
How can X happen?
How could X happen?
How can it be possible for X to happen?
- Use a negative query, -atom, when the question is NOT negated or when the user asks about a proposition that holds, happens, is true, is derived, is included, or is observed.
Typical question patterns:
Why X?
Why is X true?
Why is X the case?
Why does X happen?
Why did X happen?
Why does someone do X?
What makes X be Y?
Why is someone X?
Why is X derived?
Why is X included?
Why is X present?
Why does X hold?
Why can X happen?
Why is X possible?
Why is X necessary?
- Multiple-query rule:
Generate multiple ASP queries only when the question explicitly asks multiple independent propositions.
Indicators of multiple propositions:
- and
- or
- but
- separate questions
- separate sentences
A single proposition must produce exactly one ASP query (positive or negative).
Use multiple query format.
Typical question patterns:
Why not X and not Y? -> X Y
Why not X and why Y? -> X -Y
Why X? Why not Y? -> -X Y
Why X and why Y? -> -X -Y
Important:
The question may contain words such as "necessary", "prevents", "requires", "allows", or "due to". Do not select the reason, cause, condition, requirement, explanation, or justification suggested by those words.
Select the proposition that the question is about.
Output format:
Return the ASP query string.
Do not use Markdown, code fences or quotes.
Use ONLY atoms that are in the available atom list. Do NOT use atoms from the examples.
EXAMPLE 1 START
Input:
Query:
Why is Gabriel not innocent?
Available atoms with labels:
[
{
"atom": "person(gabriel)",
"label": "gabriel is a person"
},
{
"atom": "person(clare)",
"label": "clare is a person"
},
{
"atom": "drive(clare)",
"label": "clare is driving"
},
{
"atom": "drive(gabriel)",
"label": "gabriel is driving"
},
{
"atom": "alcohol(gabriel)",
"label": "gabriel is drinking alcohol"
},
{
"atom": "resist(gabriel)",
"label": "gabriel is resisting the authorities"
},
{
"atom": "resist(clare)",
"label": "clare is resisting the authorities"
},
{
"atom": "punish(gabriel)",
"label": "gabriel is punished"
},
{
"atom": "punish(clare)",
"label": "clare is punished"
},
{
"atom": "sentence(gabriel,innocent)",
"label": "gabriel is innocent"
},
{
"atom": "sentence(clare,innocent)",
"label": "clare is innocent"
},
{
"atom": "sentence(gabriel,prison)",
"label": "gabriel is sentenced to prison"
},
{
"atom": "sentence(clare,prison)",
"label": "clare is sentenced to prison"
}
]
Output:
sentence(gabriel,innocent)
EXAMPLE 1 END
EXAMPLE 2 START
Input:
Query:
Why is Gabriel sentences to prison?
Available atoms with labels:
[
{
"atom": "person(gabriel)",
"label": "gabriel is a person"
},
{
"atom": "person(clare)",
"label": "clare is a person"
},
{
"atom": "drive(clare)",
"label": "clare is driving"
},
{
"atom": "drive(gabriel)",
"label": "gabriel is driving"
},
{
"atom": "alcohol(gabriel)",
"label": "gabriel is drinking alcohol"
},
{
"atom": "resist(gabriel)",
"label": "gabriel is resisting the authorities"
},
{
"atom": "resist(clare)",
"label": "clare is resisting the authorities"
},
{
"atom": "punish(gabriel)",
"label": "gabriel is punished"
},
{
"atom": "punish(clare)",
"label": "clare is punished"
},
{
"atom": "sentence(gabriel,innocent)",
"label": "gabriel is innocent"
},
{
"atom": "sentence(clare,innocent)",
"label": "clare is innocent"
},
{
"atom": "sentence(gabriel,prison)",
"label": "gabriel is sentenced to prison"
},
{
"atom": "sentence(clare,prison)",
"label": "clare is sentenced to prison"
}
]
Output:
-sentence(gabriel,prison)
EXAMPLE 2 END
Explanation graph to NL Explanation¶
You are an assistant that explains why a certain outcome did or did not happen in a decision system that used Answer Set Programming.
You will receive a graph in JSON format that represents the difference between an original situation (ref program and ref model)
and a modified situation (foil program and foil model) that achieves the user’s desired result (the query).
Your task is to produce a short, clear, non-technical explanation that helps an end user understand what needed to change and why those changes make the desired outcome possible.
How to interpret the input:
Each node corresponds to a rule or an atom.
Each node may appear in both situations, only one situation, or in none.
If something appears only in the reference program, it was removed. (Check "program key")
If it appears only in the foil program, it was added.
If something is in both models are background information and should only be mentioned if helpful.
If something is in one model then it was due to the change and relevant on the explanation.
If a constraint is marked with "fired": true, it represents something that was blocking the desired outcome.
What your explanation should focus on:
First, identify what the user wants to achieve (the query).
Then, identify what was preventing this outcome.
Explain all changes needed.
Your explanation should describe how the changes prevent that blockage.
Finally, explain how those changes lead to the desired outcome.
When labels are available, you may use them to describe facts and rules in natural language.
Important constraints:
Do not mention logic programs, rules, models, literals, negation, graphs, nodes, or edges.
Do not mention the terms "reference" or "foil".
Do not quote or restate formal expressions.
Do not explain how the system works internally.
Use everyday language.
Explain changes in terms of actions, conditions, or situations.
Focus on what needs to change and why.
The explanation should be written as a simple sentence with nice flow
not a structured list should focus on cause and effect rather than system mechanics.
EXAMPLE 1 START
Input:
"graph": {
"nodes": [
{
"type": "atom",
"id": "person(gabriel)",
"models": ["foil", "ref"],
"programs": ["foil", "ref"],
"label": "{} is a person"
},
{
"type": "atom",
"id": "person(clare)",
"models": ["foil", "ref"],
"programs": ["foil", "ref"],
"label": "{} is a person"
},
{
"type": "atom",
"id": "drive(clare)",
"models": ["foil", "ref"],
"programs": ["foil", "ref"],
"label": "{} is driving"
},
{
"type": "atom",
"id": "drive(gabriel)",
"models": ["ref"],
"programs": ["foil", "ref"],
"label": "{} is driving"
},
{
"type": "atom",
"id": "alcohol(gabriel)",
"models": ["foil", "ref"],
"programs": ["foil", "ref"],
"label": "{} is drinking alcohol"
},
{
"type": "atom",
"id": "resist(gabriel)",
"models": [],
"programs": ["foil", "ref"],
"label": "{} is resisting the authorities"
},
{
"type": "atom",
"id": "resist(clare)",
"models": [],
"programs": ["foil", "ref"],
"label": "{} is resisting the authorities"
},
{
"type": "atom",
"id": "punish(gabriel)",
"models": ["ref"],
"programs": ["foil", "ref"],
"label": "{} is punished"
},
{
"type": "atom",
"id": "punish(clare)",
"models": [],
"programs": ["foil", "ref"],
"label": "{} is punished"
},
{
"type": "atom",
"id": "sentence(gabriel,innocent)",
"models": ["foil"],
"programs": ["foil", "ref"],
"label": "{} is innocent"
},
{
"type": "atom",
"id": "sentence(clare,innocent)",
"models": ["foil", "ref"],
"programs": ["foil", "ref"],
"label": "{} is innocent"
},
{
"type": "atom",
"id": "sentence(gabriel,prison)",
"models": ["ref"],
"programs": ["foil", "ref"],
"label": "{} is sentenced to prison"
},
{
"type": "atom",
"id": "sentence(clare,prison)",
"models": [],
"programs": ["foil", "ref"],
"label": "{} is sentenced to prison"
},
{
"type": "rule(disjunction)",
"id": "1",
"models": ["foil", "ref"],
"programs": ["foil", "ref"],
"first_order": "person(gabriel).",
"fact": True
},
{
"type": "rule(disjunction)",
"id": "2",
"models": ["foil", "ref"],
"programs": ["foil", "ref"],
"first_order": "person(clare).",
"fact": True
},
{
"type": "rule(disjunction)",
"id": "3",
"models": [],
"programs": [],
"first_order": "resist(P) :- person(P).",
"label": "{} is resisting the authorities"
},
{
"type": "rule(disjunction)",
"id": "4",
"models": [],
"programs": [],
"first_order": "resist(P) :- person(P).",
"label": "{} is resisting the authorities"
},
{
"type": "rule(disjunction)",
"id": "5",
"models": ["foil", "ref"],
"programs": ["foil", "ref"],
"removable": True,
"first_order": "drive(clare).",
"fact": True
},
{
"type": "rule(disjunction)",
"id": "6",
"models": ["foil", "ref"],
"programs": ["foil", "ref"],
"removable": True,
"first_order": "alcohol(gabriel).",
"fact": True
},
{
"type": "rule(disjunction)",
"id": "7",
"models": ["ref"],
"programs": ["ref"],
"removable": True,
"first_order": "drive(gabriel).",
"fact": True
},
{
"type": "rule(disjunction)",
"id": "8",
"models": ["ref"],
"programs": ["foil", "ref"],
"first_order": "punish(P) :- drive(P); alcohol(P); person(P).",
"label": "{} is punished since they drove drunk"
},
{
"type": "rule(disjunction)",
"id": "9",
"models": [],
"programs": ["foil", "ref"],
"first_order": "punish(P) :- resist(P); person(P).",
"label": "{} is punished since they resisted the authorities "
},
{
"type": "rule(disjunction)",
"id": "10",
"models": [],
"programs": ["foil", "ref"],
"first_order": "punish(P) :- resist(P); person(P).",
"label": "{} is punished since they resisted the authorities "
},
{
"type": "rule(disjunction)",
"id": "11",
"models": ["foil"],
"programs": ["foil", "ref"],
"first_order": "sentence(P,innocent) :- person(P); not punish(P).",
"label": "{} is innocent since they were not punished"
},
{
"type": "rule(disjunction)",
"id": "12",
"models": ["foil", "ref"],
"programs": ["foil", "ref"],
"first_order": "sentence(P,innocent) :- person(P); not punish(P).",
"label": "{} is innocent since they were not punished"
},
{
"type": "rule(disjunction)",
"id": "13",
"models": ["ref"],
"programs": ["foil", "ref"],
"first_order": "sentence(P,prison) :- punish(P).",
"label": "{} is innocent since they were not punished"
},
{
"type": "rule(disjunction)",
"id": "14",
"models": [],
"programs": ["foil", "ref"],
"first_order": "sentence(P,prison) :- punish(P).",
"label": "{} is innocent since they were not punished"
}
],
"edges": [
{"type": "positive", "source": "1", "target": "person(gabriel)"},
{"type": "positive", "source": "2", "target": "person(clare)"},
{"type": "positive", "source": "3", "target": "resist(gabriel)"},
{"type": "positive", "source": "4", "target": "resist(clare)"},
{"type": "positive", "source": "5", "target": "drive(clare)"},
{"type": "positive", "source": "6", "target": "alcohol(gabriel)"},
{"type": "positive", "source": "7", "target": "drive(gabriel)"},
{"type": "positive", "source": "8", "target": "punish(gabriel)"},
{"type": "positive", "source": "9", "target": "punish(gabriel)"},
{"type": "positive", "source": "10", "target": "punish(clare)"},
{"type": "positive", "source": "11", "target": "sentence(gabriel,innocent)"},
{"type": "positive", "source": "12", "target": "sentence(clare,innocent)"},
{"type": "positive", "source": "13", "target": "sentence(gabriel,prison)"},
{"type": "positive", "source": "14", "target": "sentence(clare,prison)"},
{"type": "negative", "source": "punish(gabriel)", "target": "11"},
{"type": "negative", "source": "punish(clare)", "target": "12"},
{"type": "positive", "source": "person(gabriel)", "target": "3"},
{"type": "positive", "source": "person(clare)", "target": "4"},
{"type": "positive", "source": "person(gabriel)", "target": "8"},
{"type": "positive", "source": "alcohol(gabriel)", "target": "8"},
{"type": "positive", "source": "drive(gabriel)", "target": "8"},
{"type": "positive", "source": "person(gabriel)", "target": "9"},
{"type": "positive", "source": "resist(gabriel)", "target": "9"},
{"type": "positive", "source": "person(clare)", "target": "10"},
{"type": "positive", "source": "resist(clare)", "target": "10"},
{"type": "positive", "source": "person(gabriel)", "target": "11"},
{"type": "positive", "source": "person(clare)", "target": "12"},
{"type": "positive", "source": "punish(gabriel)", "target": "13"},
{"type": "positive", "source": "punish(clare)", "target": "14"}
],
"query": [
{
"query_atom": "sentence(gabriel,innocent)",
"type": "positive"
}
]
}
Output:
{
"explanation": "
For gabriel to receive an innocent sentence he shouldn't have had alcohol. He was driving, so him also having alcohol causes punishment that leads to him being sentenced to prison."
"
}
EXAMPLE 1 END