Moots¶
gutenberg_kg.serve.entmoot
¶
Entmoot: two books from the corpus hold a conversation.
Each book is a :class:Tree. On its turn a tree retrieves passages from its
own book only (the worker's corpus="<genre>/<book>" scope), answers the
other tree from those passages through a local model, and cites them with
[sN] tags. A grounding check verifies the tags against the passages it
was given this turn, and its quoted phrases against every passage its own book
has given it during the moot. A tree may also quote the other tree, tagging the
words [oN] with the turn they were spoken in; those quotes are checked and
reported separately and never count toward grounding. After the check, every
quote that was not found is snapped (:func:snap_quotes): replaced with its
source's exact words when it nearly matches them, or stripped of its quotation
marks when it does not, so every quote left in a transcript is verbatim.
This module is pure logic: the worker client and the text synthesizer are
passed in, so tests run with fakes and no network. The gutenkg converse
command wires in the real ones. Nothing here writes to any graph; a moot's
only output is its transcript.
Moot(client, synth, keys, topic, turns, *, k=6, model=None, model_name='', temperature=0.6)
¶
A moot between two books, held one turn at a time.
:meth:start makes both trees' opening retrievals; each :meth:next_turn
speaks one utterance. The CLI runs it to the end in :func:run_moot; the
worker holds one per live moot and runs a turn per request.
Both trees first retrieve on the topic alone (or on their own book name
with no topic); that opening retrieval names each tree, and the first
speaker opens from its passages. Every later turn retrieves on
:func:retrieval_query. Each tree keeps a pool of every passage its own
book has returned during the moot, against which its quotes are checked.
The rolling summary is rebuilt before every turn whose index is a
positive multiple of :data:SUMMARY_EVERY.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
A |
required | |
synth
|
A |
required | |
keys
|
list[str]
|
The two books' catalog keys, in speaking order. |
required |
topic
|
str | None
|
The seed topic, or |
required |
turns
|
int
|
Total utterances. |
required |
k
|
int
|
Passages per turn. |
6
|
model
|
str | None
|
Model id passed to |
None
|
model_name
|
str
|
Model id recorded in the transcript. |
''
|
temperature
|
float
|
Sampling temperature. |
0.6
|
Raises:
| Type | Description |
|---|---|
ValueError
|
When the same book key is given twice. |
Source code in src/gutenberg_kg/serve/entmoot.py
897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914 915 916 917 918 919 920 921 | |
done
property
¶
Whether every turn has been spoken.
next_turn()
¶
Speak the next utterance.
Returns:
| Type | Description |
|---|---|
Utterance
|
The utterance, also appended to the transcript. |
Raises:
| Type | Description |
|---|---|
RuntimeError
|
Before :meth: |
Source code in src/gutenberg_kg/serve/entmoot.py
952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 | |
start()
¶
Make both trees' opening retrievals and name the trees.
Returns:
| Type | Description |
|---|---|
Transcript
|
The transcript, with its trees and no utterances yet. |
Source code in src/gutenberg_kg/serve/entmoot.py
928 929 930 931 932 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 | |
MootError
¶
Bases: RuntimeError
Raised when a moot cannot continue, such as when the model returns nothing.
Passage(tag, label, node_id, content)
dataclass
¶
One retrieved passage, tagged for citation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tag
|
str
|
Citation tag, |
required |
label
|
str
|
Section label: the entry date for a diary, the chunk name
( |
required |
node_id
|
str
|
The passage's node id in its knowledge graph. |
required |
content
|
str
|
Passage text. |
required |
Transcript(trees, topic, model, settings, utterances=list(), summaries=list())
dataclass
¶
A finished moot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
trees
|
list[Tree]
|
The trees, in speaking order. |
required |
topic
|
str | None
|
The seed topic, or |
required |
model
|
str
|
Model id used for every call. |
required |
settings
|
dict
|
The run's settings (turns, k, temperature, max_tokens). |
required |
utterances
|
list[Utterance]
|
Every utterance, in order. |
list()
|
summaries
|
list[dict]
|
|
list()
|
stats()
¶
Grounding totals across the moot.
Returns:
| Type | Description |
|---|---|
dict
|
Counts and rates, measured on the model's raw replies.
|
Source code in src/gutenberg_kg/serve/entmoot.py
171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 | |
to_dict()
¶
The transcript as plain JSON-ready data.
Returns:
| Type | Description |
|---|---|
dict
|
Trees, topic, model, settings, utterances, summaries and stats. |
Source code in src/gutenberg_kg/serve/entmoot.py
229 230 231 232 233 234 235 236 237 238 239 240 241 242 | |
Tree(key, title, author, genre)
dataclass
¶
One book taking part in a moot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The book's catalog key, |
required |
title
|
str
|
Book title. |
required |
author
|
str
|
Book author, or |
required |
genre
|
str
|
Genre name. |
required |
Utterance(turn, speaker, title, text, cited, quotes, grounding, grounded, attempts, query, retrieved, raw_text='', snaps=list(), intent='')
dataclass
¶
One line of the moot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
turn
|
int
|
Turn number in the transcript, from 1. |
required |
speaker
|
str
|
The speaking tree's key. |
required |
title
|
str
|
The speaking tree's title. |
required |
text
|
str
|
What it said. |
required |
cited
|
dict[str, str]
|
Valid |
required |
quotes
|
list[dict]
|
Each double-quoted span of 3+ words: |
required |
grounding
|
dict
|
The :func: |
required |
grounded
|
bool
|
Whether the kept reply passed the grounding check. |
required |
attempts
|
int
|
1, or 2 when the first reply failed and the turn was retried. |
required |
query
|
str
|
The retrieval query that produced this turn's passages. |
required |
retrieved
|
list[dict]
|
|
required |
raw_text
|
str
|
The model's reply before snapping; |
''
|
snaps
|
list[dict]
|
One record per quote :func: |
list()
|
intent
|
str
|
The move this turn was asked to make; see :data: |
''
|
best_span(quote, sources)
¶
Find the source span most like a quote.
Every window of the quote's word count, give or take two words, in every source is compared with the quote over normalized words.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
quote
|
str
|
The quoted span. |
required |
sources
|
list[str]
|
Source texts, as written (not normalized). |
required |
Returns:
| Type | Description |
|---|---|
tuple[str, float]
|
The best span exactly as the source writes it, edge punctuation
dropped, and its difflib ratio; |
Source code in src/gutenberg_kg/serve/entmoot.py
624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 | |
build_messages(tree, other, topic, passages, summary, recent, incoming, intent='answer')
¶
Assemble the chat messages for one turn.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tree
|
Tree
|
The speaking tree. |
required |
other
|
Tree
|
The tree it is talking to. |
required |
topic
|
str | None
|
The seed topic, or |
required |
passages
|
list[Passage]
|
This turn's passages. |
required |
summary
|
str
|
The rolling summary so far, or |
required |
recent
|
list[Utterance]
|
The utterances before |
required |
incoming
|
Utterance | None
|
The other tree's last utterance, or |
required |
intent
|
str
|
The move this turn makes, a key of :data: |
'answer'
|
Returns:
| Type | Description |
|---|---|
list[dict]
|
OpenAI-style messages. |
Source code in src/gutenberg_kg/serve/entmoot.py
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 | |
check_grounding(text, passages, pool=None, others=None, own=None)
¶
Check a reply's citations and quotes.
[sN] tags must name a passage given this turn. A quote of the
speaker's own book is found when it appears, after normalization, in any
passage of that book retrieved during the moot (pool); a quote with
an ellipsis is checked fragment by fragment (see :func:_quote_in). A
quote tagged [oN] is a quote of turn N of the moot: of the other
tree (source "other") or of the speaker's own earlier turn (source
"self"). It is valid when it appears in that turn, and it is reported
separately, never counting toward grounding: words spoken in the moot are
model-written, not the book. Words of the moot under an [sN] tag are
checked as an own-book quote, so they are a miss. A reply with no tags
has tags_valid true, so tags_cited is reported to make that visible.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The reply. |
required |
passages
|
list[Passage]
|
The passages given this turn. |
required |
pool
|
list[Passage] | None
|
Every passage of the speaker's own book retrieved during the
moot; defaults to |
None
|
others
|
dict[int, str] | None
|
The other tree's utterances so far, turn number -> text. |
None
|
own
|
dict[int, str] | None
|
The speaker's own earlier utterances, turn number -> text. |
None
|
Returns:
| Type | Description |
|---|---|
dict
|
|
Source code in src/gutenberg_kg/serve/entmoot.py
546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 | |
find_quotes(text)
¶
Every double-quoted span of 3 or more words in a reply.
Curly quotes count as double quotes. Leading and trailing punctuation inside the quote marks is dropped.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The reply. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
The quoted spans. |
Source code in src/gutenberg_kg/serve/entmoot.py
480 481 482 483 484 485 486 487 488 489 490 | |
parse_tags(text)
¶
Every citation tag in a reply, in order, lower-cased.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The reply. |
required |
Returns:
| Type | Description |
|---|---|
list[str]
|
Tags such as |
Source code in src/gutenberg_kg/serve/entmoot.py
466 467 468 469 470 471 472 | |
passages_from_hits(hits)
¶
Tag hits s1..sk for citation.
A diary hit is labeled with its entry date, so Pepys cites days. A book hit is labeled with its chunk name, since hits carry no section title.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
hits
|
list[dict]
|
Worker hits, best first. |
required |
Returns:
| Type | Description |
|---|---|
list[Passage]
|
One passage per hit. |
Source code in src/gutenberg_kg/serve/entmoot.py
269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |
persona(tree, other, topic)
¶
The system prompt for one tree.
The tree is the book, speaking in its author's voice: first person where
the author wrote in the first person, and in the author's style. With no
named author ("" or a placeholder such as Various) the book speaks
as itself.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
tree
|
Tree
|
The speaking tree. |
required |
other
|
Tree
|
The tree it is talking to. |
required |
topic
|
str | None
|
The seed topic, or |
required |
Returns:
| Type | Description |
|---|---|
str
|
The system prompt. |
Source code in src/gutenberg_kg/serve/entmoot.py
336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | |
resolve_model(backend, model, synth)
¶
Pick the model a moot calls with and the name its transcript records.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
backend
|
str
|
The synthesizer's backend name, e.g. |
required |
model
|
str | None
|
A model id the caller asked for, or |
required |
synth
|
The synthesizer, whose own default applies off omlx. |
required |
Returns:
| Type | Description |
|---|---|
tuple[str | None, str]
|
|
Source code in src/gutenberg_kg/serve/entmoot.py
852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 | |
retrieval_query(topic, incoming)
¶
The anchor rule: keep every retrieval tied to the moot's topic.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
topic
|
str | None
|
The seed topic, or |
required |
incoming
|
str
|
The other tree's last line. |
required |
Returns:
| Type | Description |
|---|---|
str
|
|
Source code in src/gutenberg_kg/serve/entmoot.py
322 323 324 325 326 327 328 329 | |
retrieve(client, tree, text, k, cited=None)
¶
Retrieve passages from the tree's own book, fresh ones first.
The strongest matches for a topic are the same every turn, so a tree
kept repeating its best lines. Passages it has already cited in the moot
now go to the back: the worker is asked for k * FRESH_OVERFETCH hits,
uncited ones are kept in rank order, and cited ones only fill what is left.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
A |
required | |
tree
|
Tree
|
The tree whose book is searched. |
required |
text
|
str
|
Retrieval query. |
required |
k
|
int
|
Passages to retrieve. |
required |
cited
|
set[str] | None
|
Node ids this tree has cited so far in the moot. |
None
|
Returns:
| Type | Description |
|---|---|
list[Passage]
|
Tagged passages, best first. |
Source code in src/gutenberg_kg/serve/entmoot.py
298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 | |
run_moot(client, synth, keys, topic, turns, *, k=6, model=None, model_name='', temperature=0.6, on_utterance=None)
¶
Hold a moot between two books, start to finish.
See :class:Moot for how each turn is made.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
client
|
A |
required | |
synth
|
A |
required | |
keys
|
list[str]
|
The two books' catalog keys, in speaking order. |
required |
topic
|
str | None
|
The seed topic, or |
required |
turns
|
int
|
Total utterances. |
required |
k
|
int
|
Passages per turn. |
6
|
model
|
str | None
|
Model id passed to |
None
|
model_name
|
str
|
Model id recorded in the transcript. |
''
|
temperature
|
float
|
Sampling temperature. |
0.6
|
on_utterance
|
Optional callback, called with each utterance as it lands. |
None
|
Returns:
| Type | Description |
|---|---|
Transcript
|
The transcript. |
Raises:
| Type | Description |
|---|---|
ValueError
|
When the same book key is given twice. |
Source code in src/gutenberg_kg/serve/entmoot.py
999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041 1042 1043 1044 1045 1046 | |
snap_quotes(text, pool, others=None, own=None)
¶
Make every quote in a reply verbatim, or not a quote.
Every double-quoted span is checked against its source (see
:func:check_grounding) and corrected in place when it misses. When
:func:best_span finds source words with a ratio of at least
:data:SNAP_MIN_RATIO, they replace the quote's words, keeping the
quote's own marks and edge punctuation. Otherwise the quotation marks are
removed and the words stay as paraphrase; so they are, unchecked, for a
missed quote with an ellipsis and for any quote under 3 words, which is
too short to check. Quotes that are found and citation tags are left alone.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
text
|
str
|
The reply. |
required |
pool
|
list[Passage]
|
Every passage of the speaker's own book retrieved during the moot. |
required |
others
|
dict[int, str] | None
|
The other tree's utterances so far, turn number -> text. |
None
|
own
|
dict[int, str] | None
|
The speaker's own earlier utterances, turn number -> text. |
None
|
Returns:
| Type | Description |
|---|---|
tuple[str, list[dict]]
|
The corrected reply, and one record per corrected quote:
|
Source code in src/gutenberg_kg/serve/entmoot.py
657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 | |
summarize(synth, summary, utterances, *, model)
¶
Rebuild the rolling summary with one complete call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
synth
|
The text synthesizer. |
required | |
summary
|
str
|
The previous summary, or |
required |
utterances
|
list[Utterance]
|
Every utterance so far. |
required |
model
|
str | None
|
Model id override, or |
required |
Returns:
| Type | Description |
|---|---|
str
|
The new summary, or the previous one if the model returned nothing. |
Source code in src/gutenberg_kg/serve/entmoot.py
822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 | |
tree_from_hits(key, hits)
¶
Build a tree from the first hit of an opening retrieval.
Enrichment puts title, author and genre on every hit. With no
hits, the title falls back to the book part of the key.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
key
|
str
|
The book's catalog key. |
required |
hits
|
list[dict]
|
Hits from a query scoped to that key. |
required |
Returns:
| Type | Description |
|---|---|
Tree
|
The tree. |
Source code in src/gutenberg_kg/serve/entmoot.py
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 | |
turn(synth, tree, other, topic, passages, summary, recent, incoming, *, number, pool, others, own=None, query, model, temperature, intent='answer')
¶
Speak one turn: complete, check grounding, retry once on failure.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
synth
|
A |
required | |
tree
|
Tree
|
The speaking tree. |
required |
other
|
Tree
|
The tree it is talking to. |
required |
topic
|
str | None
|
The seed topic, or |
required |
passages
|
list[Passage]
|
This turn's passages, already retrieved. |
required |
summary
|
str
|
The rolling summary so far. |
required |
recent
|
list[Utterance]
|
The utterances before |
required |
incoming
|
Utterance | None
|
The other tree's last utterance, or |
required |
number
|
int
|
This turn's number in the transcript, from 1. |
required |
pool
|
list[Passage]
|
Every passage of this tree's book retrieved during the moot,
including |
required |
others
|
dict[int, str]
|
The other tree's utterances so far, turn number -> text. |
required |
own
|
dict[int, str] | None
|
This tree's own earlier utterances, turn number -> text. |
None
|
query
|
str
|
The retrieval query that produced |
required |
intent
|
str
|
The move this turn makes, a key of :data: |
'answer'
|
model
|
str | None
|
Model id override, or |
required |
temperature
|
float
|
Sampling temperature. |
required |
Returns:
| Type | Description |
|---|---|
Utterance
|
The utterance, its quotes snapped by :func: |
Raises:
| Type | Description |
|---|---|
MootError
|
When the model returns no text on both attempts. |
Source code in src/gutenberg_kg/serve/entmoot.py
743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 | |
turn_intent(number, turns)
¶
The move for one turn of a moot.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
number
|
int
|
The turn's number, from 1. |
required |
turns
|
int
|
The moot's length. |
required |
Returns:
| Type | Description |
|---|---|
str
|
A key of :data: |
Source code in src/gutenberg_kg/serve/entmoot.py
400 401 402 403 404 405 406 407 408 409 410 411 412 413 | |