Extracting information from historical studies. How to work with GPT?
Regardless of whether we’re trying to extract knowledge from the GPT model itself, or whether we want the model to extract that knowledge from the context of the query provided, the construction and manner of the question (prompt) is important. A good prompt can significantly improve the accuracy of the model’s answer, and after all, accuracy is key in tasks of automatic extraction of information from historical publications. The knowledge extracted in this way is intended to be stored in databases or knowledge graphs, and the model’s output can also be used in the creation of digital editions of publications.
Prompt and Result Formatting
When GPT-4 was asked to divide the text into regests (lat. ‘regestum’, see wikipedia) it did the job quite correctly. Below is the prompt and result.
prompt: Divide the text below into regests:
1229 papież Grzegorz IX potwierdza kl. tyn. posiadanie m. in. B.
(Tyn. 11b — bulla interpol. w XV w., por. J. Wyrozumski, Państwowa gospodarka
solna w Polsce do schyłku XIV w., Kr. 1968, s. 25); a. 1286 komes Grzegorz
zw. Żegocic dz. B., zabraniał kl. dominikanów w Krakowie przeprowadzić wodę
z rz. Rudawy przez Krowodrzę, → też p. 6 (KK 1, 86); 1304–6 komes Andrzej z B.
(KK 2, 242; 1, 114 przy tym dok. wisiała jego pieczęć); 1321, 1351–2, 1363
Grzegorz z B. (AS 2, 7; Mp. 3, 761; Wp. 3, 1495; ZDM 1, 63; KMK 1, 27); 1366
Jan z B. (ZDM 1, 117); 1367 Betico kmieć z B. (KRK 2 s. 13);
1387–96 Jan, Jaśko z Goźlic [pow. sand.], B. i Ossolina [pow. sand.], kaszt.
wiśl. 1388–96, wg Długosza zabity przez Grota ze Słupczy [pow. sand.] w sporze
o granice wsi Dwikozy [pow. sand.] (AGZ 5, 16; AS 2, 61; KK 2, 392, 401; Mp. 4,
1022; ZDM 6, 1540, 1548, 1584; KUJ 1, 8; AKP 8, 63; Proch. 12; DH 3 s. 552 na
s. 372 i 466 wiadomości odnoszą się do kaszt. wiśl. Mikołaja z Bogorii [pow.
sand.], a nie z Ossolina; MH 10 s. 148–9; Bon. 1 s. 86); a. 1396 kaszt. wiśl.
Jaśko za zasługi przy chrystianizacji Litwy dostał od Władysława Jag. m.
Wojsław [nie zid.] i kilka wsi w ziemi krak. (J. Ossoliński, Pamiętnik
1595–1621, Wr. 1952, s. 4);
(Note: text in Polish).
1. Regest 1229: Papież Grzegorz IX potwierdza klasztorowi tynieckiemu posiadanie miejscowości, w tym B. (źródło: Tyniec, bulla interpolowana w XV wieku; porównaj: J. Wyrozumski, “Państwowa gospodarka solna w Polsce do schyłku XIV w.”, Kraków, 1968, str. 25)
2. Regest około 1286: Komes Grzegorz zwany Żegocic dziedziczy B., zabrania klasztorowi dominikanów w Krakowie przeprowadzić wodę z rzeki Rudawy przez Krowodrzę (źródło: KK 1, 86; zobacz też: str. 6)
3. Regest 1304–6: Komes Andrzej z B. (źródła: KK 2, 242; 1, 114 — jego pieczęć wisiała przy tym dokumencie)
4. Regest 1321, 1351–2, 1363: Grzegorz z B. (źródła: AS 2, 7; Mp. 3, 761; Wp. 3, 1495; ZDM 1, 63; KMK 1, 27)
5. Regest 1366: Jan z B. (źródło: ZDM 1, 117)
6. Regest 1367: Betico, kmieć z B. (źródło: KRK 2 str. 13)
7. Regest 1387–96: Jan, Jaśko z Goźlic [powiat sandomierski], dziedzice B. i Ossolina [powiat sandomierski], kasztelan wiślicki w latach 1388–96, według Długosza, zabity przez Grota ze Słupczy [powiat sandomierski] w sporze o granice wsi Dwikozy [powiat sandomierski] (źródła: AGZ 5, 16; AS 2, 61; KK 2, 392, 401; Mp. 4, 1022; ZDM 6, 1540, 1548, 1584; KUJ 1, 8; AKP 8, 63; Proch. 12; DH 3 str. 552 — na str. 372 i 466 wiadomości odnoszą się do kasztelana wiślickiego Mikołaja z Bogorii [powiat sandomierski], a nie z Ossolina; MH 10 str. 148–9; Bon. 1 str. 86)
8. Regest około 1396: Kasztelan wiślicki Jaśko za zasługi przy chrystianizacji Litwy otrzymał od Władysława Jagiełły miasto Wojsław [niezidentyfikowane] i kilka wsi w ziemi krakowskiej (źródło: J. Ossoliński, “Pamiętnik 1595–1621”, Warszawa, 1952, str. 4)
(The text used is a fragment of the ‘Balice’ entry from the Historical-Geographical Dictionary of Polish Lands http://www.slownik.ihpan.edu.pl/search.php?id=2348)
However, if we want to achieve something more, receive for example a result in the form of an XML file, with marked characters and locations appearing in the text, we must construct a more complex prompt. A precise specification of what we want to achieve is crucial: we should indicate the target data format, describe in more detail what a registry is, what tags we expect. Above all, it is important to prepare for the model examples of data (here registries) and expected results (fragments of XML):
Prompt: Please convert the text below containing regests into an XML file.
A regest begins with a date, e.g., 1245, or a date range, e.g., 1245–56,
or an approximate date, e.g., a. 1456, after which the content of the regest
follows, and after that — in parentheses — the source of information. After
the parentheses, there is a semicolon separating the regests. If there is no
date at the beginning, it means that the date is the same as in the previous
regest. In the content of the regest, mark the persons appearing therein with
the <persName> tag, places with the <placeName> tag, geographical names with
the <geogName> tag, and the names of offices, professions, or functions with
the <occupation> tag. Remember that one-letter abbreviations, such as ‘B.’,
represent the location to which the regest pertain. Persons appearing in the
content of the regest are medieval characters, who did not have surnames but
added the place from which they originate to their names, e.g., Jan from
Grzędów, in such cases mark the person with the <persName> tag including the
place.
1) Example of a regest:
a. 1396 kaszt. wiśl. Jaśko za zasługi przy chrystianizacji Litwy dostał od
Władysława Jag. m. Wojsław [nie zid.] i kilka wsi w ziemi krak. w tym wieś Grzędy w pow. sand., którą sprzedał Krzesławowi z Wolicy. Synem Jaśka był Jan z B. (J. Ossoliński, Pamiętnik 1595–1621, Wr. 1952, s. 4);
Result:
<s type=”regest”><date>a. 1396</date>
<content><occupation>kaszt. wiśl.</occupation> <persName>Jaśko</persName>
za zasługi przy chrystianizacji <geogName>Litwy</geogName> dostał od
<persName>Władysława Jag.</persName> m. <placeName>Wojsław</placeName>
[nie zid.] i kilka wsi w ziemi krak. w tym wieś <placeNme>Grzędy</PlaceName>
w pow. sand., którą sprzedał <persName>Krzesławowi z Wolicy</persName>. Synem
<persName>Jaśka</persName> był <persName>Jan z B.</persName></content>
<biblio>(J. Ossoliński, Pamiętnik 1595–1621, Wr. 1952, s. 4)</biblio>;</s>
2) Example of a regest:
1467 Albert kmieć z B. (KRK 3 s. 23);
Result:
<s type=”regest”><date>1467</date>
<content><persName>Albert</persName> <occupation>kmieć</occupation> z
<placeName>B.</placeName></content></s>
Here is the text to be processed: [text]
Such a prompt will result in the creation of a quite correct XML, with tagged characters, locations, or rivers. The downside is its size — adding the text to be processed and taking into account the expected size of the result, it is not possible to process large amounts of text in this way. Despite everything, the result is impressive — and all without programming, only through instructions and examples in natural language.
<regests>
<s type=”regest”>
<date>1229</date>
<content><persName>papież Grzegorz IX</persName> potwierdza <placeName>kl. tyn.</placeName>
posiadanie m. in. <placeName>B.</placeName></content>
<biblio>(Tyn. 11b — bulla interpol. w XV w., por. J. Wyrozumski, Państwowa gospodarka solna w Polsce do schyłku XIV w., Kr. 1968, s. 25)</biblio>;
</s>
<s type=”regest”>
<date>a. 1286</date>
<content><occupation>kom</occupation> <persName>Grzegorz zw. Żegocic</persName> dz.
<placeName>B.</placeName>, zabraniał <placeName>kl. dominikanów</placeName> w
<placeName>Krakowie</placeName> przeprowadzić wodę z rz. <geogName>Rudawy</geogName> przez
<placeName>Krowodrzę</placeName>, → też p. 6</content>
<biblio>(KK 1, 86)</biblio>;
</s>
<s type=”regest”>
<date>1304–6</date>
<content><occupation>kom</occupation> <persName>Andrzej z B.</persName></content>
<biblio>(KK 2, 242; 1, 114 przy tym dok. wisiała jego pieczęć)</biblio>;
</s>
<s type=”regest”>
<date>1321</date>
<content><persName>Grzegorz z B.</persName></content>
<biblio>(AS 2, 7; Mp. 3, 761; Wp. 3, 1495; ZDM 1, 63; KMK 1, 27)</biblio>;
</s>
<s type=”regest”>
<date>1351–2</date>
<content><persName>Grzegorz z B.</persName></content>
</s>
<s type=”regest”>
<date>1363</date>
<content><persName>Grzegorz z B.</persName></content>
</s>
<s type=”regest”>
<date>1366</date>
<content><persName>Jan z B.</persName></content>
<biblio>(ZDM 1, 117)</biblio>;
</s>
<s type=”regest”>
<date>1367</date>
<content><persName>Betico</persName> <occupation>kmieć</occupation> z <placeName>B.</placeName>
</content>
<biblio>(KRK 2 s. 13)</biblio>;
</s>
<s type=”regest”>
<date>1387–96</date>
<content><persName>Jan</persName>, <persName>Jaśko z Goźlic</persName> [pow. sand.],
<placeName>B.</placeName> i <placeName>Ossolina</placeName> [pow. sand.],
<occupation>kaszt. wiśl.</occupation> 1388–96, wg <persName>Długosza</persName> zabity
przez <persName>Grota ze Słupczy</persName> [pow. sand.] w sporze o granice wsi
<placeName>Dwikozy</placeName> [pow. sand.]</content>
<biblio>(AGZ 5, 16; AS 2, 61; KK 2, 392, 401; Mp. 4, 1022; ZDM 6, 1540, 1548, 1584; KUJ 1, 8; AKP 8, 63; Proch. 12; DH 3 s. 552 na s. 372 i 466 wiadomości odnoszą się do kaszt. wiśl. Mikołaja z Bogorii [pow. sand.], a nie z Ossolina; MH 10 s. 148–9; Bon. 1 s. 86)</biblio>;
</s>
<s type=”regest”>
<date>a. 1396</date>
<content><occupation>kaszt. wiśl.</occupation> <persName>Jaśko</persName> za zasługi
przy chrystianizacji <geogName>Litwy</geogName> dostał od <persName>Władysława Jag.</persName>
m. <placeName>Wojsław</placeName> [nie zid.] i kilka wsi w ziemi krak.</content>
<biblio>(J. Ossoliński, Pamiętnik 1595–1621, Wr. 1952, s. 4)</biblio>;
</s>
</regests>
Since the context size that GPT models are capable of processing is limited, and using the API is costly, one might consider whether it would be possible to create a hybrid solution. To divide the entry text from the SHG dictionary using more conventional methods (python, spaCy, regex, etc.) into points, subpoints, and regests, and to use the LLM model for tagging the content of the regest itself (without bibliography), perhaps additionally also for determining its subject matter, then gather everything in the form of a resulting XML file.
The difference between zero-shot and few-shot learning
A few weeks ago, I conducted a simple analysis of the accuracy of the GPT-4 model in determining family relationships based on the texts of biograms of historical figures. The model’s task was to find relationships between the main character of the biogram and the people appearing in the text of the biogram. At the time, a rather extensive prompt was used, albeit devoid of examples teaching the model what effect we expect. The prompt used in the study of a sample of 50 biograms of historical figures from the Polish Biographical Dictionary (see link ) looked like this:
Prompt: Based on the given text, search for all relatives or in-laws of
the main character of the text: {name}. Possible types of kinship: father,
mother, son, daughter, brother, sister, wife, husband, father-in-law,
mother-in-law, grandfather, grandmother, grandson, granddaughter,
brother-in-law, sister-in-law, nephew, niece, cousin.
List the results as an unnumbered list in the form:
main character -> type of kinship (who the person was to them) -> person.
Each item on a separate line. For example:
main character -> brother -> Jan Kowalski
main character -> sister -> Anna
List only types of kinship that appear in the text. If there is no information
about the kinships of the main character in the text, write: no data.
It allowed achieving about 88% accuracy on the tested sample, however, there were biograms where quite a few errors or missed relationships occurred. One of them was the biogram of the Bavarian princess Jadwiga Jagiellonka, where the model found 9 relationships, one of them wrongly, while 3 relationships were not found at all:
- Jadwiga Jagiellonka -> father -> Kazimierz Jagiellończyk (True)
- Jadwiga Jagiellonka -> mother -> Elżbieta Rakuszanka (True)
- Jadwiga Jagiellonka -> husband -> Jerzy Bawarski (True)
- Jadwiga Jagiellonka -> brother -> Władysław Jagiellończyk (True)
- Jadwiga Jagiellonka -> brother -> Aleksander Jagiellończyk (True)
- Jadwiga Jagiellonka -> brother -> Zygmunt I Jagiellończyk (True)
- Jadwiga Jagiellonka -> daughter -> Elżbieta (True)
- Jadwiga Jagiellonka -> daughter -> Małgorzata (True)
- Jadwiga Jagiellonka -> grandson -> Ruprecht hr. Palatynatu (False)
- missing -> son -> Ludwik
- missing -> son -> Ruprecht
- missing -> grandson -> (unnamed)
Could adding even one example to the prompt used to process biographies improve this result? It seems that yes — adding an example:
Example: "Soderini Carlo (c. 1537–1581), merchant and banker. He was one of
the five sons of Niccolo and Annalena Ricasoli, younger brother of Bernardo."
Result:
main character -> father -> Niccolo Ricasoli
main character -> mother -> Annalena Ricasoli
main character -> brother -> Bernardo
gave a completely correct answer:
- Jadwiga Jagiellonka -> father -> Kazimierz Jagiellończyk
- Jadwiga Jagiellonka -> mother -> Elżbieta Rakuszanka
- Jadwiga Jagiellonka -> husband -> Jerzy Bawarski
- Jadwiga Jagiellonka -> son -> Ludwik
- Jadwiga Jagiellonka -> son -> Ruprecht
- Jadwiga Jagiellonka -> daughter -> Elżbieta
- Jadwiga Jagiellonka -> daughter -> Małgorzata
- Jadwiga Jagiellonka -> brother -> Władysław Jagiellończyk
- Jadwiga Jagiellonka -> brother -> Aleksander
- Jadwiga Jagiellonka -> brother -> Zygmunt I
- Jadwiga Jagiellonka -> grandson -> unknown by name (son of Elżbieta)
(the test was conducted using ChatPGT with the GPT-4 model, in the version from May 24, 2023).
In the example above, the result was presented as a list, but a more convenient option for further processing would be the result in JSON format. The GPT model can do this, of course. Modify the prompt by replacing the passage describing the format of the result as a list with text describing the expected JSON structure. It is also useful to include an example:
Present the result as a list of JSON objects containing fields:
relationship: type of relationship (who the person was to the
hero/heroine)
person: name (name of the person related by relationship to the hero).
Example: "Soderini Carlos (ok. 1557–1589), kupiec i bankier. Był jednym
z pięciu synów Niccola i Annaleny Ricasoli, młodszym bratem Bernarda (zob.).
Jego bratanicą była Małgorzata Anna, żona Winfrida de Loeve. S. ożenił się
z Joanną, córką burgrabiego krakowskiego Adama Kurozwęckiego."
Result:
[{"relacja":"ojciec", "osoba":"Niccola"},
{"relacja":"matka", "osoba":"Annalena Ricasoli"},
{"relacja":"brat", "osoba":"Bernard"},
{"relacja":"bratanica", "osoba":"Małgorzata Anna"},
{"relacja":"żona", "osoba":"Joanna"},
{"relacja":"teść", "osoba":"Adam Kurozwęcki"}
]
The GPT-4 model processed the biography of Jadwiga Jagiellonka using a modified prompt and returned the result in JSON format:
[{"relacja":"ojciec", "osoba":"Kazimierz Jagiellończyk"},
{"relacja":"matka", "osoba":"Elżbieta Rakuszanka"},
{"relacja":"mąż", "osoba":"Jerzy Bawarski"},
{"relacja":"syn", "osoba":"Ludwik"},
{"relacja":"syn", "osoba":"Ruprecht"},
{"relacja":"córka", "osoba":"Elżbieta"},
{"relacja":"córka", "osoba":"Małgorzata"},
{"relacja":"brat", "osoba":"Władysław Jagiellończyk"},
{"relacja":"brat", "osoba":"Aleksander"},
{"relacja":"brat", "osoba":"Zygmunt I"},
{"relacja":"teść", "osoba":"Ludwik Bogaty"},
{"relacja":"zięć", "osoba":"Ruprecht hr. Palatynatu"}
]
Prompt Engineering
Constructing an effective query (prompt) for an LLM model has essentially become a completely new specialty in computer science. Job offers are appearing for people proficient in creating effective prompts, many courses and guides have been created on this topic, below are links to some of the more interesting ones:
“ChatGPT Prompt Engineering for Developers”
https://www.deeplearning.ai/short-courses/
“Prompt Engineering Guide”
https://www.promptingguide.ai/
“Learn Prompting. A Free, Open Source Course on Communicating with Artificial Intelligence”
https://learnprompting.org/
“Brex’s Prompt Engineering Guide”
https://github.com/brexhq/prompt-engineering
Python Libraries
Because processing larger amounts of text requires the use of scripts rather than manually creating and running queries, libraries that facilitate the extraction of structural data from texts through LLM models can be useful. One of them is the kor library (https://github.com/eyurtsev/kor) for Python, integrated with the LangChain framework. Based on the prepared schema and examples, kor itself creates an effective prompt that returns the expected information from the given text. An example of a schema that defines obtaining information about the father of a historical figure from the text:
relation_schema = Object(
id="relacja_ojciec",
description="wydobywanie relacji rodzinnych z tekstu",
attributes=[
Text(
id="ojciec",
description= "relacja: główny bohater -> ojciec",
examples=[
("Śniadecka Kornelia Ludwika (1802—1866), działaczka polityczna, była córką Jędrzeja Śniadeckiego", "ojciec = Jędrzej Śniadecki"),
("Ojcem Kowalskiego był Stanisław, zwany Górką, herbu Wrzask", "ojciec = Stanisław zwany Górką"),
("Adam Wacław Horzyński urodził się w 1772, jako syn Aleksandra i Ludwiki z Czartoryskich", "ojciec = Aleksander")
])
],
many=False,
)
A simple test script, for the biogram of a historical figure from the 16th century (the key fragment “Podfilipski Michał h. Ciołek (ok. 1515–1562), rotmistrz obrony potocznej, stolnik halicki. Był synem Wiktoryna (zob.) i nie znanej nam z imienia Szarawskiej. Spotykamy go po raz pierwszy w rejestrach popisowych w czerwcu 1535…”) gave a correct result:
{'relacja_ojciec': [{'ojciec': 'ojciec = Wiktoryn'}]}
Another library of this kind is Promptify: and since the field of using LLM models is developing very dynamically, there are certainly others that I have not yet had the chance to get to know and try out.