The Bash example opens this page.
relate: find relationships among named entities.
sings="sings=singer:song"
cat <<'EOF' |
[
{"name": "Paul McCartney", "kind": "singer"},
{"name": "Ringo Starr", "kind": "singer"},
{"name": "Yesterday", "kind": "song"},
{"name": "Octopus's Garden", "kind": "song"}
]
EOF
thinkthen relate "$sings" |
jq -c '[.source.name, .target.name, .probability]'["Paul McCartney","Yesterday",0.81] ["Ringo Starr","Octopus's Garden",0.88]
You give it a set of names, the kind of each name, and the relations you care about. relate reads no other text. Jev answers from what it knows about the names. You get back one edge for each related pair, with its probability. A rule asks one yes or no question for each pair. In a rules file, a rule marked "single": true asks one choice for each source instead, and gives that source at most one edge. For the links a text states, use recognize --relation.
Read the answer
A relation has a direction, or it reads the same both ways. The number on an edge is a probability. Your threshold decides which edges you keep.
| Exit code | What it means |
|---|---|
| 0 | the run finished |
| 6 | the run finished with failed questions |
| 2 | usage or input error |
| 4 | the backend failed or refused, as it does for evidence over the size limit |
| 5 | a local failure |
| 70 | a defect in the tool |
Call it from your language
import thinkthen as tt
names = [
("Paul McCartney", "singer"),
("Ringo Starr", "singer"),
("Yesterday", "song"),
("Octopus's Garden", "song"),
]
who_sings = tt.relate(
names,
relations={"sings": ("singer", "song")},
).value
sings = [
(edge.source.name, edge.target.name)
for edge in who_sings
]
assert sings == [
("Paul McCartney", "Yesterday"),
("Ringo Starr", "Octopus's Garden"),
]No Polars sample for relate.
No pandas sample for relate.
import assert from "node:assert/strict";
import * as tt from "thinkthen";
const rules = [
"Book economy class for every flight under six hours.",
"Submit receipts within 30 days of the trip.",
"Hotel stays are capped at 200 dollars a night.",
"Employees may book business class on any flight.",
"Rental cars need a manager's approval.",
"Receipts may be submitted at any time, " +
"with no deadline.",
"Meals are reimbursed up to 60 dollars a day.",
"Use the company travel portal for all bookings.",
];
const entities = rules.map(
(rule): readonly [string, string] => [rule, "rule"],
);
const contradictions = (await tt.relate(entities, {
relations: ["contradicts"],
either: ["contradicts"],
threshold: 0.5,
})).value;
const pairs = contradictions.map((edge) => [
edge.source.name,
edge.target.name,
edge.probability,
]);
assert.deepEqual(pairs, [
[rules[0], rules[3], 0.83],
[rules[1], rules[5], 0.97],
]);require "thinkthen"
rules = [
"Book economy class for every flight under six hours.",
"Submit receipts within 30 days of the trip.",
"Hotel stays are capped at 200 dollars a night.",
"Employees may book business class on any flight.",
"Rental cars need a manager's approval.",
"Receipts may be submitted at any time, " \
"with no deadline.",
"Meals are reimbursed up to 60 dollars a day.",
"Use the company travel portal for all bookings."
]
entities = rules.map { |rule| [rule, "rule"] }
contradictions = ThinkThen.relate(
entities,
relations: ["contradicts"],
either: ["contradicts"],
threshold: 0.5
).value
pairs = contradictions.map do |edge|
[edge.source.name, edge.target.name, edge.probability]
end
raise unless pairs == [
[rules[0], rules[3], 0.83],
[rules[1], rules[5], 0.97]
]library(thinkthen)
rules <- c(
"Book economy class for every flight under six hours.",
"Submit receipts within 30 days of the trip.",
"Hotel stays are capped at 200 dollars a night.",
"Employees may book business class on any flight.",
"Rental cars need a manager's approval.",
paste0(
"Receipts may be submitted at any time, ",
"with no deadline."
),
"Meals are reimbursed up to 60 dollars a day.",
"Use the company travel portal for all bookings."
)
entities <- data.frame(name = rules, kind = "rule")
contradictions <- tt_relate(
entities,
either = "contradicts",
threshold = 0.5
)$value
stopifnot(identical(contradictions$source, rules[c(1, 2)]))
stopifnot(identical(contradictions$target, rules[c(4, 6)]))
stopifnot(identical(
contradictions$probability,
c(0.83, 0.97)
))use thinkthen::{Engine, Entity, Relate, RelationRule};
let tt = Engine::from_env()?;
let rules = [
"Book economy class for every flight under six hours.",
"Submit receipts within 30 days of the trip.",
"Hotel stays are capped at 200 dollars a night.",
"Employees may book business class on any flight.",
"Rental cars need a manager's approval.",
concat!(
"Receipts may be submitted at any time, ",
"with no deadline.",
),
"Meals are reimbursed up to 60 dollars a day.",
"Use the company travel portal for all bookings.",
];
let contradicts =
RelationRule::both_ways("contradicts", "*", "*")?;
let ask = Relate::builder()
.relation(contradicts)?
.threshold(0.5)?
.build()?;
let entities = rules
.iter()
.map(|rule| Entity::new(rule, "rule"))
.collect::<Result<Vec<_>, _>>()?;
let contradictions = tt.relate(&ask, entities)?
.into_value();
let pairs: Vec<_> = contradictions
.iter()
.map(|edge| {
let source = edge.source().name();
let target = edge.target().name();
(source, target, edge.probability())
})
.collect();
let expected = [
(rules[0], rules[3], 0.83),
(rules[1], rules[5], 0.97),
];
assert_eq!(pairs, expected);Put this code inside fn main() -> Result<(), Box<dyn std::error::Error>> and end it with Ok(()). main returns a Result, so ? compiles.
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <thinkthen.h>
thinkthen_engine *tt = thinkthen_engine_new();
assert(tt);
const char *rules[] = {
"Book economy class for every flight under six hours.",
"Submit receipts within 30 days of the trip.",
"Hotel stays are capped at 200 dollars a night.",
"Employees may book business class on any flight.",
"Rental cars need a manager's approval.",
"Receipts may be submitted at any time, "
"with no deadline.",
"Meals are reimbursed up to 60 dollars a day.",
"Use the company travel portal for all bookings.",
};
char records[8][96];
const char *texts[8];
size_t lengths[8];
for (int i = 0; i < 8; i++) {
snprintf(
records[i],
sizeof records[i],
"{\"name\": \"%s\", \"kind\": \"rule\"}",
rules[i]
);
texts[i] = records[i];
lengths[i] = strlen(records[i]);
}
const char *spec =
"{\"version\": 1, \"relate\": {\"relations\": [{"
"\"name\": \"contradicts\", \"source\": \"*\", "
"\"target\": \"*\", \"either\": true}]}, "
"\"threshold\": 0.5}";
char *contradictions;
size_t contradictions_len;
int rc = thinkthen_relate(
tt,
spec,
texts,
lengths,
8,
&contradictions,
&contradictions_len
);
assert(rc == THINKTHEN_OK);
assert(strstr(contradictions, "\"probability\":0.83"));
assert(strstr(contradictions, "\"probability\":0.97"));
thinkthen_free_string(contradictions);
thinkthen_engine_free(tt);Keep the #include lines on top. Put the rest inside int main(void) and end it with return 0;.
No C++ sample for relate.
No Objective-C sample for relate.
No COBOL sample for relate.
No Ada sample for relate.
No Java sample for relate.
No Kotlin sample for relate.
No Scala sample for relate.
No C# sample for relate.
No Go sample for relate.
No Swift sample for relate.
No Zig sample for relate.
No PHP sample for relate.
No Dart sample for relate.
LOAD './thinkthen.duckdb_extension';
CREATE TABLE rules AS FROM (VALUES
(1, 'Book economy class for every flight ' ||
'under six hours.'),
(2, 'Submit receipts within 30 days of the trip.'),
(3, 'Hotel stays are capped at 200 dollars a night.'),
(4, 'Employees may book business class on any flight.'),
(5, 'Rental cars need a manager''s approval.'),
(6, 'Receipts may be submitted at any time, ' ||
'with no deadline.'),
(7, 'Meals are reimbursed up to 60 dollars a day.'),
(8, 'Use the company travel portal for all bookings.')
) r(id, body);
SELECT * FROM thinkthen_relate(
'SELECT id, body AS name, ''rule'' AS kind FROM rules',
'{"version": 1, "relate": {"relations": [{' ||
'"name": "contradicts", "source": "*", ' ||
'"target": "*", "either": true}]}}'
) AS contradiction;relation|source|target|probability|either contradicts|1|4|0.83|true contradicts|2|6|0.97|true
.load ./thinkthen
CREATE TABLE rules(id INTEGER, body TEXT, kind TEXT);
INSERT INTO rules VALUES
(1, 'Book economy class for every flight ' ||
'under six hours.', 'rule'),
(2, 'Submit receipts within 30 days of the trip.',
'rule'),
(3, 'Hotel stays are capped at 200 dollars a night.',
'rule'),
(4, 'Employees may book business class on any flight.',
'rule'),
(5, 'Rental cars need a manager''s approval.', 'rule'),
(6, 'Receipts may be submitted at any time, ' ||
'with no deadline.', 'rule'),
(7, 'Meals are reimbursed up to 60 dollars a day.',
'rule'),
(8, 'Use the company travel portal for all bookings.',
'rule');
SELECT * FROM thinkthen_relate(
'SELECT id, body AS name, kind FROM rules',
'either:contradicts'
) AS contradiction;contradicts|1|4|0.83|1 contradicts|2|6|0.97|1
CREATE TABLE rules (
id int, body text, kind text DEFAULT 'rule'
);
INSERT INTO rules (id, body) VALUES
(1, 'Book economy class for every flight ' ||
'under six hours.'),
(2, 'Submit receipts within 30 days of the trip.'),
(3, 'Hotel stays are capped at 200 dollars a night.'),
(4, 'Employees may book business class on any flight.'),
(5, 'Rental cars need a manager''s approval.'),
(6, 'Receipts may be submitted at any time, ' ||
'with no deadline.'),
(7, 'Meals are reimbursed up to 60 dollars a day.'),
(8, 'Use the company travel portal for all bookings.');
SELECT * FROM thinkthen_relate(
'SELECT id, body AS name, kind FROM rules',
'{"version": 1, "relate": {"relations": [{' ||
'"name": "contradicts", "source": "*", ' ||
'"target": "*", "either": true}]}}'
) AS contradiction;relation | source | target | probability | either -------------+--------+--------+-------------+-------- contradicts | 1 | 4 | 0.83 | t contradicts | 2 | 6 | 0.97 | t (2 rows)
Arguments, options, and more examples
Watch relate answer questions about Beatles songs: relate asks what Jev knows about the names.
On GitHub: github.com/botassembly/thinkthen