langstate 0.2.2__tar.gz → 0.2.4__tar.gz
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.
- {langstate-0.2.2 → langstate-0.2.4}/BENCHMARK-PROVENANCE.md +7 -4
- {langstate-0.2.2 → langstate-0.2.4}/MANIFEST.in +0 -1
- {langstate-0.2.2/src/langstate.egg-info → langstate-0.2.4}/PKG-INFO +18 -4
- {langstate-0.2.2 → langstate-0.2.4}/README.md +17 -3
- {langstate-0.2.2 → langstate-0.2.4}/pyproject.toml +1 -1
- {langstate-0.2.2 → langstate-0.2.4}/src/langstate/__init__.py +2 -2
- {langstate-0.2.2 → langstate-0.2.4}/src/langstate/adapters.py +13 -4
- {langstate-0.2.2 → langstate-0.2.4}/src/langstate/cli.py +31 -5
- {langstate-0.2.2 → langstate-0.2.4}/src/langstate/compress.py +50 -16
- {langstate-0.2.2 → langstate-0.2.4}/src/langstate/validate.py +16 -4
- {langstate-0.2.2 → langstate-0.2.4/src/langstate.egg-info}/PKG-INFO +18 -4
- {langstate-0.2.2 → langstate-0.2.4}/LICENSE +0 -0
- {langstate-0.2.2 → langstate-0.2.4}/setup.cfg +0 -0
- {langstate-0.2.2 → langstate-0.2.4}/src/langstate.egg-info/SOURCES.txt +0 -0
- {langstate-0.2.2 → langstate-0.2.4}/src/langstate.egg-info/dependency_links.txt +0 -0
- {langstate-0.2.2 → langstate-0.2.4}/src/langstate.egg-info/entry_points.txt +0 -0
- {langstate-0.2.2 → langstate-0.2.4}/src/langstate.egg-info/requires.txt +0 -0
- {langstate-0.2.2 → langstate-0.2.4}/src/langstate.egg-info/top_level.txt +0 -0
|
@@ -16,10 +16,13 @@ Why they are excluded from current claims:
|
|
|
16
16
|
|
|
17
17
|
- The JSON does not record a run time, source commit, full model identifier, or
|
|
18
18
|
reproducible decoding/runtime configuration.
|
|
19
|
-
-
|
|
20
|
-
`
|
|
21
|
-
|
|
22
|
-
`
|
|
19
|
+
- When these rows were recorded, `bench_adapters.py` imported legacy
|
|
20
|
+
repository-root copies of `adapters.py` and `compress.py` (default models
|
|
21
|
+
qwen3:14b and claude-opus-4-7), not the shipping modules under
|
|
22
|
+
`src/langstate/`; both pairs differed at `main` commit
|
|
23
|
+
`b9a8b036ab65d0a74d27a580602cd12afd438235`. The root copies have since been
|
|
24
|
+
removed and the script now imports `langstate.*`, so a fresh run uses the
|
|
25
|
+
current shipping defaults rather than the configuration behind these bytes.
|
|
23
26
|
- `bench_results.json` names missing-fact keys such as `corpus_size`,
|
|
24
27
|
`arxiv_papers`, and `benchmarks` that are not in the current runner's
|
|
25
28
|
`GROUND_TRUTH` list. The current script therefore cannot reproduce the stored
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langstate
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Inspectable context compression for LLM conversations, with visible scaffold state and lexical fact receipts
|
|
5
5
|
Author-email: Rolando Bosch <roli@hermes-labs.ai>
|
|
6
6
|
Maintainer-email: Hermes Labs <info@hermes-labs.ai>
|
|
@@ -28,6 +28,8 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
# langstate
|
|
30
30
|
|
|
31
|
+
[](https://github.com/hermes-labs-ai/langstate/actions/workflows/test.yml)
|
|
32
|
+
|
|
31
33
|
**Compress long LLM conversations into visible working state — then check that
|
|
32
34
|
the literal facts you care about survived.**
|
|
33
35
|
|
|
@@ -49,12 +51,19 @@ semantic fidelity.
|
|
|
49
51
|
## Try it in two minutes
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
|
-
python -m pip install langstate==0.2.
|
|
54
|
+
python -m pip install langstate==0.2.4
|
|
55
|
+
langstate --version
|
|
53
56
|
langstate demo
|
|
54
57
|
```
|
|
55
58
|
|
|
59
|
+
Or install the CLI from the [Hermes Labs Homebrew tap](https://github.com/hermes-labs-ai/homebrew-tap):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
brew install hermes-labs-ai/tap/langstate
|
|
63
|
+
```
|
|
64
|
+
|
|
56
65
|
It prints the compressed messages and a machine-readable receipt showing that
|
|
57
|
-
both named facts survived. The proof needs no model, API key, network call, or
|
|
66
|
+
both named facts survived and that the history got smaller. The proof needs no model, API key, network call, or
|
|
58
67
|
Ollama installation.
|
|
59
68
|
|
|
60
69
|
Python 3.10+; no runtime dependencies beyond the standard library. To use the
|
|
@@ -147,7 +156,7 @@ contract; automatic fact extraction is a convenience heuristic.
|
|
|
147
156
|
|
|
148
157
|
## How it works
|
|
149
158
|
|
|
150
|
-
For conversations of at least six user/assistant
|
|
159
|
+
For conversations of at least six user/assistant turn pairs, `compress`:
|
|
151
160
|
|
|
152
161
|
1. retains all `system` messages and the requested recent suffix verbatim;
|
|
153
162
|
2. sends the older non-system messages to the selected summarizer;
|
|
@@ -209,5 +218,10 @@ compress(
|
|
|
209
218
|
validate(before, after, facts=None)
|
|
210
219
|
```
|
|
211
220
|
|
|
221
|
+
`preserve_recent` counts recent user-initiated turns. Each retained turn includes
|
|
222
|
+
its assistant messages, tool calls, and tool results verbatim, so the retained
|
|
223
|
+
tail does not begin in the middle of a tool exchange. Older history remains
|
|
224
|
+
a lossy summary; retain the original messages when exact replay is required.
|
|
225
|
+
|
|
212
226
|
See `Receipt.as_dict()` for a JSON-friendly receipt. The package is
|
|
213
227
|
Apache-2.0 licensed.
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# langstate
|
|
2
2
|
|
|
3
|
+
[](https://github.com/hermes-labs-ai/langstate/actions/workflows/test.yml)
|
|
4
|
+
|
|
3
5
|
**Compress long LLM conversations into visible working state — then check that
|
|
4
6
|
the literal facts you care about survived.**
|
|
5
7
|
|
|
@@ -21,12 +23,19 @@ semantic fidelity.
|
|
|
21
23
|
## Try it in two minutes
|
|
22
24
|
|
|
23
25
|
```bash
|
|
24
|
-
python -m pip install langstate==0.2.
|
|
26
|
+
python -m pip install langstate==0.2.4
|
|
27
|
+
langstate --version
|
|
25
28
|
langstate demo
|
|
26
29
|
```
|
|
27
30
|
|
|
31
|
+
Or install the CLI from the [Hermes Labs Homebrew tap](https://github.com/hermes-labs-ai/homebrew-tap):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
brew install hermes-labs-ai/tap/langstate
|
|
35
|
+
```
|
|
36
|
+
|
|
28
37
|
It prints the compressed messages and a machine-readable receipt showing that
|
|
29
|
-
both named facts survived. The proof needs no model, API key, network call, or
|
|
38
|
+
both named facts survived and that the history got smaller. The proof needs no model, API key, network call, or
|
|
30
39
|
Ollama installation.
|
|
31
40
|
|
|
32
41
|
Python 3.10+; no runtime dependencies beyond the standard library. To use the
|
|
@@ -119,7 +128,7 @@ contract; automatic fact extraction is a convenience heuristic.
|
|
|
119
128
|
|
|
120
129
|
## How it works
|
|
121
130
|
|
|
122
|
-
For conversations of at least six user/assistant
|
|
131
|
+
For conversations of at least six user/assistant turn pairs, `compress`:
|
|
123
132
|
|
|
124
133
|
1. retains all `system` messages and the requested recent suffix verbatim;
|
|
125
134
|
2. sends the older non-system messages to the selected summarizer;
|
|
@@ -181,5 +190,10 @@ compress(
|
|
|
181
190
|
validate(before, after, facts=None)
|
|
182
191
|
```
|
|
183
192
|
|
|
193
|
+
`preserve_recent` counts recent user-initiated turns. Each retained turn includes
|
|
194
|
+
its assistant messages, tool calls, and tool results verbatim, so the retained
|
|
195
|
+
tail does not begin in the middle of a tool exchange. Older history remains
|
|
196
|
+
a lossy summary; retain the original messages when exact replay is required.
|
|
197
|
+
|
|
184
198
|
See `Receipt.as_dict()` for a JSON-friendly receipt. The package is
|
|
185
199
|
Apache-2.0 licensed.
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "langstate"
|
|
7
|
-
version = "0.2.
|
|
7
|
+
version = "0.2.4"
|
|
8
8
|
description = "Inspectable context compression for LLM conversations, with visible scaffold state and lexical fact receipts"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = "Apache-2.0"
|
|
@@ -3,5 +3,5 @@
|
|
|
3
3
|
from langstate.compress import compress
|
|
4
4
|
from langstate.validate import Receipt, extract_facts, validate
|
|
5
5
|
|
|
6
|
-
__version__ = "0.2.
|
|
7
|
-
__all__ = ["
|
|
6
|
+
__version__ = "0.2.4"
|
|
7
|
+
__all__ = ["Receipt", "__version__", "compress", "extract_facts", "validate"]
|
|
@@ -17,12 +17,13 @@ Production targets (one cheap default per provider, per the Hermes spec):
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
+
import http.client
|
|
20
21
|
import json
|
|
21
22
|
import os
|
|
22
23
|
import time
|
|
23
24
|
import urllib.error
|
|
24
25
|
import urllib.request
|
|
25
|
-
from
|
|
26
|
+
from collections.abc import Callable
|
|
26
27
|
|
|
27
28
|
Summarizer = Callable[[str], str]
|
|
28
29
|
|
|
@@ -82,7 +83,7 @@ DEFAULT_OPENAI_MODEL = "gpt-4o-mini"
|
|
|
82
83
|
|
|
83
84
|
def openai(
|
|
84
85
|
model: str = DEFAULT_OPENAI_MODEL,
|
|
85
|
-
api_key:
|
|
86
|
+
api_key: str | None = None,
|
|
86
87
|
url: str = OPENAI_URL,
|
|
87
88
|
max_tokens: int = 4000,
|
|
88
89
|
temperature: float = 0.2,
|
|
@@ -138,7 +139,7 @@ ANTHROPIC_VERSION = "2023-06-01"
|
|
|
138
139
|
|
|
139
140
|
def anthropic(
|
|
140
141
|
model: str = DEFAULT_ANTHROPIC_MODEL,
|
|
141
|
-
api_key:
|
|
142
|
+
api_key: str | None = None,
|
|
142
143
|
url: str = ANTHROPIC_URL,
|
|
143
144
|
max_tokens: int = 4000,
|
|
144
145
|
temperature: float = 0.2,
|
|
@@ -210,7 +211,15 @@ def probe(name: str) -> dict:
|
|
|
210
211
|
out = fn("Say OK.")
|
|
211
212
|
except AdapterUnavailable as e:
|
|
212
213
|
return {"name": name, "available": False, "reason": str(e), "latency_ms": None}
|
|
213
|
-
except
|
|
214
|
+
except (
|
|
215
|
+
AttributeError,
|
|
216
|
+
http.client.HTTPException,
|
|
217
|
+
IndexError,
|
|
218
|
+
KeyError,
|
|
219
|
+
RuntimeError,
|
|
220
|
+
TypeError,
|
|
221
|
+
ValueError,
|
|
222
|
+
) as e:
|
|
214
223
|
return {"name": name, "available": False, "reason": f"{type(e).__name__}: {e}", "latency_ms": None}
|
|
215
224
|
return {
|
|
216
225
|
"name": name,
|
|
@@ -10,16 +10,42 @@ from . import __version__, compress, validate
|
|
|
10
10
|
|
|
11
11
|
|
|
12
12
|
def _demo_summary(_: str) -> str:
|
|
13
|
-
return "- Project: Atlas
|
|
13
|
+
return "- Project: Atlas (Q4 build-cluster migration)\n- Budget: $4,000 cap, no overrun"
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
def _demo_payload() -> dict[str, object]:
|
|
17
17
|
before = [
|
|
18
18
|
{"role": "system", "content": "Preserve named project facts."},
|
|
19
|
-
{
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
{
|
|
20
|
+
"role": "user",
|
|
21
|
+
"content": (
|
|
22
|
+
"We are kicking off the Q4 infrastructure work. For tracking, "
|
|
23
|
+
"the project is called Atlas, and it covers moving every build "
|
|
24
|
+
"job from the old cluster to the new one before the freeze."
|
|
25
|
+
),
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
"role": "assistant",
|
|
29
|
+
"content": (
|
|
30
|
+
"Recorded Project: Atlas, the Q4 migration of all build jobs "
|
|
31
|
+
"from the old cluster to the new one ahead of the freeze."
|
|
32
|
+
),
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"role": "user",
|
|
36
|
+
"content": (
|
|
37
|
+
"Finance signed off yesterday. The budget is $4,000 for the "
|
|
38
|
+
"whole migration, contractor hours included, and we cannot go "
|
|
39
|
+
"above that without a fresh approval."
|
|
40
|
+
),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
"role": "assistant",
|
|
44
|
+
"content": (
|
|
45
|
+
"Recorded Budget: $4,000, all-inclusive, with no overrun "
|
|
46
|
+
"allowed without a fresh approval."
|
|
47
|
+
),
|
|
48
|
+
},
|
|
23
49
|
{"role": "user", "content": "What remains?"},
|
|
24
50
|
{"role": "assistant", "content": "Review the release packet."},
|
|
25
51
|
]
|
|
@@ -11,13 +11,13 @@ Usage:
|
|
|
11
11
|
response = client.chat.completions.create(messages=compressed, model="gpt-4o")
|
|
12
12
|
"""
|
|
13
13
|
|
|
14
|
-
from
|
|
14
|
+
from collections.abc import Callable
|
|
15
15
|
|
|
16
16
|
from langstate import adapters as _adapters
|
|
17
17
|
|
|
18
18
|
OLLAMA_URL = "http://localhost:11434/api/chat"
|
|
19
19
|
DEFAULT_MODEL = "qwen3:4b"
|
|
20
|
-
PRESERVE_RECENT = 4 # keep last N user/assistant
|
|
20
|
+
PRESERVE_RECENT = 4 # keep last N user/assistant turn pairs verbatim
|
|
21
21
|
|
|
22
22
|
Summarizer = Callable[[str], str]
|
|
23
23
|
|
|
@@ -58,28 +58,43 @@ def compress(
|
|
|
58
58
|
model: str = DEFAULT_MODEL,
|
|
59
59
|
ollama_url: str = OLLAMA_URL,
|
|
60
60
|
min_turns_to_compress: int = 6,
|
|
61
|
-
summarizer:
|
|
61
|
+
summarizer: Summarizer | None = None,
|
|
62
62
|
) -> list[dict]:
|
|
63
63
|
"""Summarize older messages while keeping system and recent turns verbatim.
|
|
64
64
|
|
|
65
65
|
Args:
|
|
66
66
|
messages: List of dicts with "role" and "content" keys.
|
|
67
|
-
preserve_recent: Number of recent user
|
|
67
|
+
preserve_recent: Number of recent user-initiated turns to keep verbatim,
|
|
68
|
+
including their assistant messages and tool exchanges.
|
|
68
69
|
model: Ollama model to use when no summarizer is given (back-compat).
|
|
69
70
|
ollama_url: Ollama API endpoint (back-compat).
|
|
70
|
-
min_turns_to_compress: Don't compress if fewer than this many
|
|
71
|
-
user
|
|
71
|
+
min_turns_to_compress: Don't compress if fewer than this many
|
|
72
|
+
user-initiated turns are present.
|
|
72
73
|
summarizer: Optional ``summarize(prompt) -> str`` callable. If None, falls back
|
|
73
74
|
to a local Ollama adapter (qwen3:4b) for back-compat with the MVP.
|
|
74
75
|
|
|
75
76
|
Returns:
|
|
76
77
|
Compressed messages list in the same OpenAI format.
|
|
78
|
+
|
|
79
|
+
Raises:
|
|
80
|
+
ValueError: If ``preserve_recent`` is not a non-negative integer.
|
|
81
|
+
TypeError: If the summarizer returns a non-string.
|
|
82
|
+
RuntimeError: If the summarizer returns empty or whitespace-only text.
|
|
77
83
|
"""
|
|
84
|
+
if (
|
|
85
|
+
isinstance(preserve_recent, bool)
|
|
86
|
+
or not isinstance(preserve_recent, int)
|
|
87
|
+
or preserve_recent < 0
|
|
88
|
+
):
|
|
89
|
+
raise ValueError(
|
|
90
|
+
f"preserve_recent must be a non-negative integer, got {preserve_recent!r}"
|
|
91
|
+
)
|
|
92
|
+
|
|
78
93
|
if not messages:
|
|
79
94
|
return []
|
|
80
95
|
|
|
81
|
-
#
|
|
82
|
-
n_turns = sum(
|
|
96
|
+
# Tool-call assistant messages do not start additional user turns.
|
|
97
|
+
n_turns = sum(m.get("role") == "user" for m in messages)
|
|
83
98
|
if n_turns < min_turns_to_compress:
|
|
84
99
|
return list(messages)
|
|
85
100
|
|
|
@@ -90,14 +105,21 @@ def compress(
|
|
|
90
105
|
if not conv_msgs:
|
|
91
106
|
return list(messages)
|
|
92
107
|
|
|
93
|
-
#
|
|
94
|
-
#
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
108
|
+
# Keep entire user-initiated turns, including assistant tool calls and
|
|
109
|
+
# their results. Counting raw messages can split a tool exchange.
|
|
110
|
+
if preserve_recent:
|
|
111
|
+
user_indexes = [
|
|
112
|
+
index for index, message in enumerate(conv_msgs)
|
|
113
|
+
if message.get("role") == "user"
|
|
114
|
+
]
|
|
115
|
+
if len(user_indexes) <= preserve_recent:
|
|
116
|
+
return list(messages)
|
|
117
|
+
recent_start = user_indexes[-preserve_recent]
|
|
118
|
+
history_msgs = conv_msgs[:recent_start]
|
|
119
|
+
recent_msgs = conv_msgs[recent_start:]
|
|
120
|
+
else:
|
|
121
|
+
history_msgs = conv_msgs
|
|
122
|
+
recent_msgs = []
|
|
101
123
|
|
|
102
124
|
# Format history for summarization
|
|
103
125
|
history_text = "\n".join(
|
|
@@ -109,6 +131,18 @@ def compress(
|
|
|
109
131
|
summarizer = _adapters.ollama(model=model, url=ollama_url)
|
|
110
132
|
summary = summarizer(_build_compression_prompt(history_text))
|
|
111
133
|
|
|
134
|
+
# Refuse to replace history with nothing: an empty scaffold would silently
|
|
135
|
+
# drop the compressed messages instead of summarizing them.
|
|
136
|
+
if not isinstance(summary, str):
|
|
137
|
+
raise TypeError(
|
|
138
|
+
f"summarizer must return str, got {type(summary).__name__}"
|
|
139
|
+
)
|
|
140
|
+
if not summary.strip():
|
|
141
|
+
raise RuntimeError(
|
|
142
|
+
f"summarizer returned empty output; refusing to drop "
|
|
143
|
+
f"{len(history_msgs)} messages without a summary"
|
|
144
|
+
)
|
|
145
|
+
|
|
112
146
|
# Build compressed messages
|
|
113
147
|
compressed = []
|
|
114
148
|
|
|
@@ -57,6 +57,17 @@ def _norm(s: str) -> str:
|
|
|
57
57
|
return _WS.sub(" ", s).strip().lower()
|
|
58
58
|
|
|
59
59
|
|
|
60
|
+
def _contains_fact(haystack: str, key: str) -> bool:
|
|
61
|
+
"""True iff normalized ``key`` occurs in ``haystack`` at lexical boundaries.
|
|
62
|
+
|
|
63
|
+
Plain substring membership would credit ``42`` against ``142`` or ``API``
|
|
64
|
+
against ``rapid``. Requiring a non-word (or string-edge) character on both
|
|
65
|
+
sides of the match blocks those, while leaving punctuation-led facts like
|
|
66
|
+
``$4,000`` / ``90.1%`` and multiword facts untouched.
|
|
67
|
+
"""
|
|
68
|
+
return re.search(r"(?<!\w)" + re.escape(key) + r"(?!\w)", haystack) is not None
|
|
69
|
+
|
|
70
|
+
|
|
60
71
|
def _messages_text(messages: list[dict]) -> str:
|
|
61
72
|
return "\n".join(m.get("content", "") or "" for m in messages)
|
|
62
73
|
|
|
@@ -164,9 +175,10 @@ def validate(
|
|
|
164
175
|
(heuristic — pass an explicit list for a precise receipt).
|
|
165
176
|
|
|
166
177
|
Returns:
|
|
167
|
-
A :class:`Receipt`. A fact "survived" if its normalized text
|
|
168
|
-
|
|
169
|
-
conservative check that under-counts rather than over-claims
|
|
178
|
+
A :class:`Receipt`. A fact "survived" if its normalized text occurs
|
|
179
|
+
at word boundaries in the concatenated, normalized ``after`` content —
|
|
180
|
+
a lexical, conservative check that under-counts rather than over-claims
|
|
181
|
+
(``42`` does not survive inside ``142``, nor ``API`` inside ``rapid``).
|
|
170
182
|
"""
|
|
171
183
|
if facts is None:
|
|
172
184
|
facts = extract_facts(_messages_text(before))
|
|
@@ -176,7 +188,7 @@ def validate(
|
|
|
176
188
|
dropped: list[str] = []
|
|
177
189
|
for fact in facts:
|
|
178
190
|
key = _norm(fact)
|
|
179
|
-
if key and key
|
|
191
|
+
if key and _contains_fact(after_text, key):
|
|
180
192
|
survived.append(fact)
|
|
181
193
|
else:
|
|
182
194
|
dropped.append(fact)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: langstate
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
4
4
|
Summary: Inspectable context compression for LLM conversations, with visible scaffold state and lexical fact receipts
|
|
5
5
|
Author-email: Rolando Bosch <roli@hermes-labs.ai>
|
|
6
6
|
Maintainer-email: Hermes Labs <info@hermes-labs.ai>
|
|
@@ -28,6 +28,8 @@ Dynamic: license-file
|
|
|
28
28
|
|
|
29
29
|
# langstate
|
|
30
30
|
|
|
31
|
+
[](https://github.com/hermes-labs-ai/langstate/actions/workflows/test.yml)
|
|
32
|
+
|
|
31
33
|
**Compress long LLM conversations into visible working state — then check that
|
|
32
34
|
the literal facts you care about survived.**
|
|
33
35
|
|
|
@@ -49,12 +51,19 @@ semantic fidelity.
|
|
|
49
51
|
## Try it in two minutes
|
|
50
52
|
|
|
51
53
|
```bash
|
|
52
|
-
python -m pip install langstate==0.2.
|
|
54
|
+
python -m pip install langstate==0.2.4
|
|
55
|
+
langstate --version
|
|
53
56
|
langstate demo
|
|
54
57
|
```
|
|
55
58
|
|
|
59
|
+
Or install the CLI from the [Hermes Labs Homebrew tap](https://github.com/hermes-labs-ai/homebrew-tap):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
brew install hermes-labs-ai/tap/langstate
|
|
63
|
+
```
|
|
64
|
+
|
|
56
65
|
It prints the compressed messages and a machine-readable receipt showing that
|
|
57
|
-
both named facts survived. The proof needs no model, API key, network call, or
|
|
66
|
+
both named facts survived and that the history got smaller. The proof needs no model, API key, network call, or
|
|
58
67
|
Ollama installation.
|
|
59
68
|
|
|
60
69
|
Python 3.10+; no runtime dependencies beyond the standard library. To use the
|
|
@@ -147,7 +156,7 @@ contract; automatic fact extraction is a convenience heuristic.
|
|
|
147
156
|
|
|
148
157
|
## How it works
|
|
149
158
|
|
|
150
|
-
For conversations of at least six user/assistant
|
|
159
|
+
For conversations of at least six user/assistant turn pairs, `compress`:
|
|
151
160
|
|
|
152
161
|
1. retains all `system` messages and the requested recent suffix verbatim;
|
|
153
162
|
2. sends the older non-system messages to the selected summarizer;
|
|
@@ -209,5 +218,10 @@ compress(
|
|
|
209
218
|
validate(before, after, facts=None)
|
|
210
219
|
```
|
|
211
220
|
|
|
221
|
+
`preserve_recent` counts recent user-initiated turns. Each retained turn includes
|
|
222
|
+
its assistant messages, tool calls, and tool results verbatim, so the retained
|
|
223
|
+
tail does not begin in the middle of a tool exchange. Older history remains
|
|
224
|
+
a lossy summary; retain the original messages when exact replay is required.
|
|
225
|
+
|
|
212
226
|
See `Receipt.as_dict()` for a JSON-friendly receipt. The package is
|
|
213
227
|
Apache-2.0 licensed.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|