cpu-llm 1.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. cpu_llm/__init__.py +3 -0
  2. cpu_llm/agents/__init__.py +5 -0
  3. cpu_llm/agents/deep_search.py +67 -0
  4. cpu_llm/agents/discovery.py +138 -0
  5. cpu_llm/agents/gather.py +184 -0
  6. cpu_llm/agents/imagine.py +149 -0
  7. cpu_llm/agents/reply.py +334 -0
  8. cpu_llm/agents/summarize.py +85 -0
  9. cpu_llm/assembly.py +93 -0
  10. cpu_llm/audit/__init__.py +3 -0
  11. cpu_llm/audit/entailment.py +110 -0
  12. cpu_llm/catalog.py +144 -0
  13. cpu_llm/channels.py +409 -0
  14. cpu_llm/checkpoints/vectorprism.pt +0 -0
  15. cpu_llm/checkpoints/vectorprism.pt.json +31 -0
  16. cpu_llm/classifier.py +143 -0
  17. cpu_llm/clauses.py +138 -0
  18. cpu_llm/cognition/__init__.py +13 -0
  19. cpu_llm/cognition/adapter.py +416 -0
  20. cpu_llm/comparison.py +153 -0
  21. cpu_llm/consolidation/__init__.py +5 -0
  22. cpu_llm/consolidation/sleep.py +357 -0
  23. cpu_llm/cortex_sleep.py +211 -0
  24. cpu_llm/cpu_guard.py +19 -0
  25. cpu_llm/data/artificial-intelligence.md +139 -0
  26. cpu_llm/data/wikitext-2-raw-v1/PROVENANCE.txt +17 -0
  27. cpu_llm/data/wikitext-2-raw-v1/test-PROVENANCE.txt +21 -0
  28. cpu_llm/data/wikitext-2-raw-v1/test.parquet +0 -0
  29. cpu_llm/data/wikitext-2-raw-v1/validation.parquet +0 -0
  30. cpu_llm/documents.py +301 -0
  31. cpu_llm/geometry/__init__.py +19 -0
  32. cpu_llm/geometry/poincare.py +144 -0
  33. cpu_llm/grounding.py +109 -0
  34. cpu_llm/ingest.py +89 -0
  35. cpu_llm/ir/__init__.py +41 -0
  36. cpu_llm/ir/schema.py +240 -0
  37. cpu_llm/manifest_spans.py +83 -0
  38. cpu_llm/measurements.py +480 -0
  39. cpu_llm/pipeline.py +1746 -0
  40. cpu_llm/proposition_catalog.py +40 -0
  41. cpu_llm/realizer/__init__.py +3 -0
  42. cpu_llm/realizer/inflect.py +95 -0
  43. cpu_llm/realizer/surface.py +232 -0
  44. cpu_llm/relations.py +310 -0
  45. cpu_llm/retrieval.py +264 -0
  46. cpu_llm/source.py +113 -0
  47. cpu_llm/stack.py +67 -0
  48. cpu_llm/taxonomy.py +95 -0
  49. cpu_llm/thinker_pool.py +395 -0
  50. cpu_llm/word_taxonomy.py +325 -0
  51. cpu_llm-1.7.0.dist-info/METADATA +368 -0
  52. cpu_llm-1.7.0.dist-info/RECORD +55 -0
  53. cpu_llm-1.7.0.dist-info/WHEEL +5 -0
  54. cpu_llm-1.7.0.dist-info/entry_points.txt +2 -0
  55. cpu_llm-1.7.0.dist-info/top_level.txt +1 -0
cpu_llm/__init__.py ADDED
@@ -0,0 +1,3 @@
1
+ """cpu-llm vertical slice. CPU-only; this package never imports torch."""
2
+
3
+ __version__ = "1.7.0"
@@ -0,0 +1,5 @@
1
+ from cpu_llm.agents.discovery import run_discovery
2
+ from cpu_llm.agents.imagine import compose_imagination
3
+ from cpu_llm.agents.reply import compose_reply
4
+
5
+ __all__ = ["compose_imagination", "compose_reply", "run_discovery"]
@@ -0,0 +1,67 @@
1
+ """Every chunk a deep-search prompt can reach, with none removed.
2
+
3
+ The seeds are the prompt's content words, not the words that asked for the
4
+ search. A seed keeps every paragraph stored under it, in every article. The
5
+ relation walk then keeps every paragraph of the words one, two, and three
6
+ edges further. The checked sentence is not replaced.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ from typing import Any
12
+
13
+ from cpu_llm.catalog import article_slug
14
+ from cpu_llm.channels import _content_words
15
+ from cpu_llm.relations import relation_hands
16
+
17
+ _NOT_SEEDS = frozenset(
18
+ "deep search deeply everywhere possible possibilities dive look all".split()
19
+ )
20
+
21
+
22
+ def collect_possibilities(
23
+ message: str,
24
+ word_tags: dict[str, dict[str, list[str]]],
25
+ edges: list[Any] | None = None,
26
+ ) -> dict[str, Any]:
27
+ """Return every paragraph the seeds and their relation hands name."""
28
+ seeds = [word for word in _content_words(message) if word not in _NOT_SEEDS]
29
+ found: dict[str, dict[str, Any]] = {}
30
+ for seed in seeds:
31
+ _add_word(found, word_tags, seed, "word")
32
+ hands = {1: [], 2: [], 3: []}
33
+ if edges and seeds:
34
+ hands = relation_hands(edges, seeds, limit=100_000)
35
+ for hand, words in hands.items():
36
+ for word in words:
37
+ _add_word(found, word_tags, word, f"hand-{hand}")
38
+ possibilities = [found[document_id] for document_id in sorted(found)]
39
+ return {
40
+ "seeds": seeds,
41
+ "count": len(possibilities),
42
+ "hands": {str(hand): list(words) for hand, words in hands.items()},
43
+ "possibilities": possibilities,
44
+ }
45
+
46
+
47
+ def _add_word(
48
+ found: dict[str, dict[str, Any]],
49
+ word_tags: dict[str, dict[str, list[str]]],
50
+ word: str,
51
+ via: str,
52
+ ) -> None:
53
+ for document_ids in (word_tags.get(word) or {}).values():
54
+ for document_id in document_ids:
55
+ item = found.setdefault(
56
+ document_id,
57
+ {
58
+ "document_id": document_id,
59
+ "article": article_slug(document_id),
60
+ "matched_words": [],
61
+ "via": [],
62
+ },
63
+ )
64
+ if word not in item["matched_words"]:
65
+ item["matched_words"].append(word)
66
+ if via not in item["via"]:
67
+ item["via"].append(via)
@@ -0,0 +1,138 @@
1
+ """Discovery Loop, stages 1 through 4.
2
+
3
+ The agent records what the turn already established. It does not add a
4
+ conclusion, and it does not call a model. Stage 5 was not in the protocol
5
+ text that defined this agent, so the loop stops before scale shifting.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import re
11
+ from typing import Any
12
+
13
+ PROTOCOL_VERSION = "1.0"
14
+ INVARIANT = "Reality and evidence have the final veto."
15
+
16
+ _PERSPECTIVES: dict[str, tuple[str, ...]] = {
17
+ "individual_humans": ("human", "person", "patient", "individual"),
18
+ "groups": ("society", "group", "community"),
19
+ "institutions": ("institution", "government", "agency"),
20
+ "artificial_agents": ("agent",),
21
+ "animals": ("animal", "species"),
22
+ "ecosystems": ("ecosystem", "ecology"),
23
+ "engineered_systems": ("machine", "engine", "device"),
24
+ "planetary": ("planet", "cosmic", "climate"),
25
+ }
26
+
27
+
28
+ def run_discovery(state: dict[str, Any]) -> dict[str, Any]:
29
+ claim = state.get("message") or ""
30
+ related = list(state.get("related") or [])
31
+ questions = [str(item) for item in state.get("gather_questions") or []]
32
+ evidence_text = " ".join(item.get("chunk_text") or "" for item in related)
33
+ return {
34
+ "protocol": "discovery-loop",
35
+ "version": PROTOCOL_VERSION,
36
+ "invariant": INVARIANT,
37
+ "stages_completed": [1, 2, 3, 4],
38
+ "stopped_before": "Stage 5: Scale Shifting",
39
+ "claim": _claim(claim, related, state),
40
+ "assumptions": _assumptions(state),
41
+ "information": _information(claim, related, questions, state),
42
+ "perspectives": _perspectives(f"{claim} {evidence_text}"),
43
+ }
44
+
45
+
46
+ def _claim(claim: str, related: list[dict[str, Any]], state: dict[str, Any]) -> dict[str, Any]:
47
+ return {
48
+ "text": claim,
49
+ "true_if": [
50
+ "A proposition paired with the retrieved tensor is realized and the auditor accepts the sentence.",
51
+ "Every number in that sentence matches a PrismManifest evidence span.",
52
+ ],
53
+ "false_if": [
54
+ "The prompt is blocked.",
55
+ "Deliberation reports a contradiction.",
56
+ "The sentence contains a number that is not an evidence span.",
57
+ "PrismThinker returns refuse.",
58
+ ],
59
+ "scope": {
60
+ "tenant_id": state.get("tenant_id") or "cpu-llm",
61
+ "evidence_ids": [item.get("chunk_id") for item in related],
62
+ },
63
+ }
64
+
65
+
66
+ def _assumptions(state: dict[str, Any]) -> list[dict[str, str]]:
67
+ items = [
68
+ {
69
+ "kind": "technical",
70
+ "text": "Retrieval rank is topical relevance. It is not authority.",
71
+ }
72
+ ]
73
+ if state.get("cognition_status") == "needs_evidence_propositions":
74
+ items.append(
75
+ {
76
+ "kind": "logical",
77
+ "text": "A retrieved chunk is not a proposition, and it is not treated as one.",
78
+ }
79
+ )
80
+ if state.get("grammar_span_ids"):
81
+ items.append(
82
+ {
83
+ "kind": "mathematical",
84
+ "text": "A number in the sentence is allowed only when its canonical value matches an evidence span.",
85
+ }
86
+ )
87
+ if state.get("thinker_directive") == "gather":
88
+ items.append(
89
+ {
90
+ "kind": "empirical",
91
+ "text": "An unresolved gather question is missing information, not an observation.",
92
+ }
93
+ )
94
+ return items
95
+
96
+
97
+ def _information(
98
+ claim: str,
99
+ related: list[dict[str, Any]],
100
+ questions: list[str],
101
+ state: dict[str, Any],
102
+ ) -> list[dict[str, str]]:
103
+ rows: list[dict[str, str]] = [
104
+ {"kind": "hypothesis", "text": claim, "provenance": "user"},
105
+ ]
106
+ for item in related:
107
+ rows.append(
108
+ {
109
+ "kind": "reported",
110
+ "text": item.get("chunk_text") or "",
111
+ "provenance": str(item.get("chunk_id") or "vectorprism"),
112
+ }
113
+ )
114
+ status = state.get("cognition_status")
115
+ if status == "deliberated" and state.get("abstention_triggered"):
116
+ rows.append(
117
+ {
118
+ "kind": "unknown",
119
+ "text": state.get("answer") or "",
120
+ "provenance": "deliberation",
121
+ }
122
+ )
123
+ elif status == "deliberated" and state.get("answer"):
124
+ kind = "interpretation" if float(state.get("ruin_distance") or 0) == 0 else "unknown"
125
+ rows.append({"kind": kind, "text": state.get("answer") or "", "provenance": "realizer"})
126
+ for question in questions:
127
+ rows.append({"kind": "unknown", "text": question, "provenance": "prismthinker"})
128
+ return rows
129
+
130
+
131
+ def _perspectives(text: str) -> list[dict[str, str]]:
132
+ folded = text.casefold()
133
+ selected: list[dict[str, str]] = []
134
+ for name, words in _PERSPECTIVES.items():
135
+ hits = [word for word in words if re.search(rf"\b{re.escape(word)}\b", folded)]
136
+ if hits:
137
+ selected.append({"perspective": name, "named_in_evidence": ", ".join(hits)})
138
+ return selected
@@ -0,0 +1,184 @@
1
+ """Gather every paragraph of the creative article and rank its words.
2
+
3
+ The shortlist names the article. This node loads the rest of that article,
4
+ not only the lead paragraph. It collects the content words of those
5
+ paragraphs and ranks them by how often they sit in a paragraph that shares
6
+ the prompt's words. The checked sentence is not written here.
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import random
12
+ from typing import Any
13
+
14
+ import numpy as np
15
+
16
+ from cpu_llm.catalog import article_slug
17
+ from cpu_llm.channels import _content_words, _cosine, _slice
18
+ from cpu_llm.word_taxonomy import category_number
19
+ from vectorprism.tensor_contract import PSMTensorContract
20
+
21
+ _MOST = 32
22
+
23
+
24
+ def realize_ranked_paragraphs(
25
+ message: str,
26
+ rows: list[dict[str, Any]],
27
+ pool: dict[str, Any],
28
+ proposition_for,
29
+ realizer,
30
+ auditor,
31
+ *,
32
+ numbers_allowed=None,
33
+ vary: bool = False,
34
+ rng: random.Random | None = None,
35
+ avoid: set[str] | None = None,
36
+ ) -> dict[str, Any]:
37
+ """Realize the ranked paragraphs with the sentence function, then check each one.
38
+
39
+ A paragraph is skipped when it has no proposition the realizer can repeat,
40
+ when the auditor rejects the sentence, or when a number in it is not in
41
+ the gathered paragraphs. The checked answer is left alone. With ``vary``,
42
+ each call draws a different order and a different subset of the sentences
43
+ that passed those checks.
44
+ """
45
+ by_id = {str(row["document_id"]): row for row in rows}
46
+ chosen = [item for item in pool.get("paragraphs") or [] if item.get("relevance", 0) > 0]
47
+ if not chosen:
48
+ chosen = list(pool.get("paragraphs") or [])[:1]
49
+ links: list[dict[str, Any]] = []
50
+ sentences: list[str] = []
51
+ for item in chosen:
52
+ document_id = str(item["document_id"])
53
+ row = by_id.get(document_id)
54
+ if row is None:
55
+ continue
56
+ text = str(row.get("chunk_text") or "")
57
+ proposition = proposition_for(document_id, text, row.get("tensor_1024d"))
58
+ if proposition is None:
59
+ continue
60
+ sentence, delivered = realizer.synthesize([proposition], [])
61
+ if not sentence or not auditor.audit_delivered_output(delivered, [proposition]):
62
+ continue
63
+ if numbers_allowed is not None and not numbers_allowed(sentence):
64
+ continue
65
+ prompt = set(_content_words(message))
66
+ keywords = [word for word in _content_words(sentence) if word in prompt]
67
+ links.append(
68
+ {
69
+ "chunk_id": document_id,
70
+ "sentence": sentence,
71
+ "keywords": keywords,
72
+ "relevance": item.get("relevance", 0),
73
+ }
74
+ )
75
+ if vary:
76
+ links = _varied_passage(links, rng or random.SystemRandom(), set(avoid or ()))
77
+ sentences = [item["sentence"] for item in links]
78
+ return {
79
+ "text": " ".join(sentences),
80
+ "links": links,
81
+ "realized": len(sentences),
82
+ "varied": vary,
83
+ }
84
+
85
+
86
+ def _varied_passage(
87
+ links: list[dict[str, Any]],
88
+ rng: random.Random,
89
+ avoid: set[str],
90
+ ) -> list[dict[str, Any]]:
91
+ """Pick an arrangement that is not one already returned for this prompt."""
92
+ if len(links) < 2:
93
+ return links
94
+ limit = min(len(links), 3)
95
+ for _ in range(48):
96
+ count = rng.randint(1, limit)
97
+ picks = rng.sample(list(links), count)
98
+ if _passage(picks) not in avoid:
99
+ return picks
100
+ for shift in range(len(links)):
101
+ rotated = links[shift:] + links[:shift]
102
+ for count in range(1, limit + 1):
103
+ picks = rotated[:count]
104
+ if _passage(picks) not in avoid:
105
+ return picks
106
+ return links
107
+
108
+
109
+ def _passage(links: list[dict[str, Any]]) -> str:
110
+ return " ".join(item["sentence"] for item in links)
111
+
112
+
113
+ def gather_article(
114
+ message: str,
115
+ chunks: list[dict[str, Any]],
116
+ query: np.ndarray | None = None,
117
+ ) -> dict[str, Any]:
118
+ """Rank the words and paragraphs in ``chunks`` against ``message``.
119
+
120
+ ``chunks`` carry ``document_id`` and ``chunk_text``. A ``tensor`` is used
121
+ only as a dense-core tie break. Every chunk stays in the group.
122
+ """
123
+ prompt = list(dict.fromkeys(_content_words(message)))
124
+ prompt_set = set(prompt)
125
+ articles: list[str] = []
126
+ paragraphs: list[dict[str, Any]] = []
127
+ locations: dict[str, list[str]] = {}
128
+ for chunk in chunks:
129
+ document_id = str(chunk["document_id"])
130
+ slug = article_slug(document_id)
131
+ if slug not in articles:
132
+ articles.append(slug)
133
+ words = list(dict.fromkeys(_content_words(str(chunk.get("chunk_text") or ""))))
134
+ present = set(words)
135
+ shared = [word for word in prompt if word in present]
136
+ cosine = _dense_cosine(query, chunk.get("tensor") or chunk.get("tensor_1024d"))
137
+ paragraphs.append(
138
+ {
139
+ "document_id": document_id,
140
+ "article": slug,
141
+ "relevance": len(shared),
142
+ "prompt_words": shared,
143
+ "cosine": cosine,
144
+ }
145
+ )
146
+ for word in words:
147
+ locations.setdefault(word, [])
148
+ if document_id not in locations[word]:
149
+ locations[word].append(document_id)
150
+ supporting = {item["document_id"] for item in paragraphs if item["relevance"] > 0}
151
+ if not supporting:
152
+ supporting = {item["document_id"] for item in paragraphs}
153
+ ranked_words = []
154
+ for word, document_ids in locations.items():
155
+ support = sum(1 for document_id in document_ids if document_id in supporting)
156
+ score = support + (2 if word in prompt_set else 0)
157
+ ranked_words.append((score, word, document_ids))
158
+ ranked_words.sort(key=lambda item: (-item[0], item[1]))
159
+ paragraphs.sort(
160
+ key=lambda item: (
161
+ -item["relevance"],
162
+ -(item["cosine"] if item["cosine"] is not None else -2.0),
163
+ item["document_id"],
164
+ )
165
+ )
166
+ return {
167
+ "articles": articles,
168
+ "categories": {slug: category_number(slug) for slug in articles},
169
+ "paragraph_count": len(paragraphs),
170
+ "word_count": len(ranked_words),
171
+ "paragraphs": paragraphs,
172
+ "most_relevant": [
173
+ {"word": word, "score": score, "paragraphs": len(document_ids)}
174
+ for score, word, document_ids in ranked_words[:_MOST]
175
+ ],
176
+ }
177
+
178
+
179
+ def _dense_cosine(query: np.ndarray | None, tensor: Any) -> float | None:
180
+ if query is None or tensor is None:
181
+ return None
182
+ left = _slice(np.asarray(query, dtype=np.float64).reshape(-1), PSMTensorContract.DENSE_CORE)
183
+ right = _slice(np.asarray(tensor, dtype=np.float64).reshape(-1), PSMTensorContract.DENSE_CORE)
184
+ return _cosine(left, right)
@@ -0,0 +1,149 @@
1
+ """An imagined passage from one article's stored tensors.
2
+
3
+ The shortlist names the articles. Every paragraph of those articles is loaded.
4
+ PrismResonance scores each paragraph against the question. A paragraph whose
5
+ interference is not positive is left out. The paragraphs that stay are ordered
6
+ by a walk along their dense cores: each step is the nearest unused paragraph.
7
+ The sentence copied from a paragraph is one already written there. The words
8
+ named on a step are the question's content words that occur in that sentence.
9
+
10
+ This is not a cause, a measurement, or a proposition, and it is not the
11
+ checked sentence.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ from typing import Any
17
+
18
+ import numpy as np
19
+ from vectorprism.tensor_contract import PSMTensorContract
20
+
21
+ from cpu_llm.catalog import article_slug
22
+ from cpu_llm.channels import _best_sentence, _content_words, _cosine, _slice
23
+
24
+ _NOTE = (
25
+ "Each sentence is copied from a paragraph of an article the shortlist already named. "
26
+ "PrismResonance keeps a paragraph whose interference with the question is positive. "
27
+ "Dense-core cosine orders those paragraphs. The link is that the tensors sit near each other."
28
+ )
29
+ _EMPTY = "The shortlist named no article, so there is nothing to connect."
30
+ _SILENT = "None of the loaded paragraphs interfered with the question."
31
+
32
+
33
+ def compose_imagination(
34
+ message: str,
35
+ chunks: list[dict[str, Any]],
36
+ resonance: dict[str, float],
37
+ ) -> dict[str, Any]:
38
+ """Walk ``chunks`` whose resonance scores are positive.
39
+
40
+ ``chunks`` carry ``document_id``, ``chunk_text``, and ``tensor``.
41
+ ``resonance`` maps a document id to the PrismResonance score.
42
+ """
43
+ if not chunks:
44
+ return _result([], [], _EMPTY)
45
+ kept: list[dict[str, Any]] = []
46
+ for chunk in chunks:
47
+ document_id = str(chunk["document_id"])
48
+ score = resonance.get(document_id)
49
+ if score is None or not np.isfinite(score) or float(score) <= 0.0:
50
+ continue
51
+ tensor = np.asarray(chunk["tensor"], dtype=np.float64).reshape(-1)
52
+ kept.append(
53
+ {
54
+ "document_id": document_id,
55
+ "chunk_text": str(chunk["chunk_text"]),
56
+ "tensor": tensor,
57
+ "resonance": float(score),
58
+ }
59
+ )
60
+ if not kept:
61
+ return _result(_articles(chunks), [], _SILENT, loaded=len(chunks))
62
+ ordered = _walk(kept)
63
+ links: list[dict[str, Any]] = []
64
+ keywords: list[str] = []
65
+ sentences: list[str] = []
66
+ for item in ordered:
67
+ sentence = _best_sentence(item["chunk_text"], message)
68
+ words = _shared_keywords(message, sentence)
69
+ for word in words:
70
+ if word not in keywords:
71
+ keywords.append(word)
72
+ cosine = item["cosine_to_previous"]
73
+ links.append(
74
+ {
75
+ "chunk_id": item["document_id"],
76
+ "resonance": item["resonance"],
77
+ "cosine_to_previous": None if cosine is None else float(cosine),
78
+ "sentence": sentence,
79
+ "keywords": words,
80
+ }
81
+ )
82
+ if sentence:
83
+ sentences.append(sentence)
84
+ return _result(_articles(chunks), links, _NOTE, loaded=len(chunks), keywords=keywords, text=" ".join(sentences))
85
+
86
+
87
+ def _result(
88
+ articles: list[str],
89
+ links: list[dict[str, Any]],
90
+ note: str,
91
+ *,
92
+ loaded: int = 0,
93
+ keywords: list[str] | None = None,
94
+ text: str = "",
95
+ ) -> dict[str, Any]:
96
+ return {
97
+ "triggered": True,
98
+ "articles": articles,
99
+ "loaded": loaded,
100
+ "links": links,
101
+ "keywords": list(keywords or []),
102
+ "text": text,
103
+ "note": note,
104
+ }
105
+
106
+
107
+ def _articles(chunks: list[dict[str, Any]]) -> list[str]:
108
+ slugs: list[str] = []
109
+ for chunk in chunks:
110
+ slug = article_slug(str(chunk["document_id"]))
111
+ if slug not in slugs:
112
+ slugs.append(slug)
113
+ return slugs
114
+
115
+
116
+ def _shared_keywords(message: str, sentence: str) -> list[str]:
117
+ present = set(_content_words(sentence))
118
+ words: list[str] = []
119
+ for word in _content_words(message):
120
+ if word in present and word not in words:
121
+ words.append(word)
122
+ return words
123
+
124
+
125
+ def _dense(tensor: np.ndarray) -> np.ndarray:
126
+ return _slice(tensor, PSMTensorContract.DENSE_CORE)
127
+
128
+
129
+ def _walk(kept: list[dict[str, Any]]) -> list[dict[str, Any]]:
130
+ remaining = sorted(kept, key=lambda item: (-item["resonance"], item["document_id"]))
131
+ start = remaining.pop(0)
132
+ start["cosine_to_previous"] = None
133
+ ordered = [start]
134
+ while remaining:
135
+ current = _dense(ordered[-1]["tensor"])
136
+ best_at = 0
137
+ best_rank: tuple[float, str] | None = None
138
+ best_cosine = None
139
+ for index, item in enumerate(remaining):
140
+ cosine = _cosine(current, _dense(item["tensor"]))
141
+ rank = (-(cosine if cosine is not None else -2.0), item["document_id"])
142
+ if best_rank is None or rank < best_rank:
143
+ best_rank = rank
144
+ best_at = index
145
+ best_cosine = cosine
146
+ chosen = remaining.pop(best_at)
147
+ chosen["cosine_to_previous"] = best_cosine
148
+ ordered.append(chosen)
149
+ return ordered