rfnry-engine 0.1.0__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.
- rfnry_engine-0.1.0/.gitignore +15 -0
- rfnry_engine-0.1.0/PKG-INFO +30 -0
- rfnry_engine-0.1.0/README.md +6 -0
- rfnry_engine-0.1.0/pyproject.toml +72 -0
- rfnry_engine-0.1.0/src/rfnry_engine/__init__.py +152 -0
- rfnry_engine-0.1.0/src/rfnry_engine/bundle.py +35 -0
- rfnry_engine-0.1.0/src/rfnry_engine/chain.py +37 -0
- rfnry_engine-0.1.0/src/rfnry_engine/engine.py +956 -0
- rfnry_engine-0.1.0/src/rfnry_engine/errors.py +34 -0
- rfnry_engine-0.1.0/src/rfnry_engine/fingerprint.py +41 -0
- rfnry_engine-0.1.0/src/rfnry_engine/folders.py +92 -0
- rfnry_engine-0.1.0/src/rfnry_engine/frontmatter.py +85 -0
- rfnry_engine-0.1.0/src/rfnry_engine/jail.py +24 -0
- rfnry_engine-0.1.0/src/rfnry_engine/models.py +388 -0
- rfnry_engine-0.1.0/src/rfnry_engine/py.typed +0 -0
- rfnry_engine-0.1.0/src/rfnry_engine/records.py +144 -0
- rfnry_engine-0.1.0/src/rfnry_engine/refinery/__init__.py +0 -0
- rfnry_engine-0.1.0/src/rfnry_engine/refinery/conflicts.py +37 -0
- rfnry_engine-0.1.0/src/rfnry_engine/refinery/export.py +155 -0
- rfnry_engine-0.1.0/src/rfnry_engine/refinery/holdout.py +14 -0
- rfnry_engine-0.1.0/src/rfnry_engine/refinery/judge.py +140 -0
- rfnry_engine-0.1.0/src/rfnry_engine/refinery/labels.py +62 -0
- rfnry_engine-0.1.0/src/rfnry_engine/refinery/postgres.py +292 -0
- rfnry_engine-0.1.0/src/rfnry_engine/refinery/refinery.py +592 -0
- rfnry_engine-0.1.0/src/rfnry_engine/refinery/storage.py +80 -0
- rfnry_engine-0.1.0/src/rfnry_engine/stop.py +54 -0
- rfnry_engine-0.1.0/src/rfnry_engine/tools/__init__.py +0 -0
- rfnry_engine-0.1.0/src/rfnry_engine/tools/builtins.py +391 -0
- rfnry_engine-0.1.0/src/rfnry_engine/tools/declared.py +77 -0
- rfnry_engine-0.1.0/src/rfnry_engine/tools/delegate.py +192 -0
- rfnry_engine-0.1.0/src/rfnry_engine/tools/engine_tools.py +84 -0
- rfnry_engine-0.1.0/src/rfnry_engine/tools/history.py +205 -0
- rfnry_engine-0.1.0/src/rfnry_engine/tools/tools.py +109 -0
- rfnry_engine-0.1.0/src/rfnry_engine/tools/workspace.py +654 -0
- rfnry_engine-0.1.0/src/rfnry_engine/tree.py +386 -0
- rfnry_engine-0.1.0/tests/conftest.py +60 -0
- rfnry_engine-0.1.0/tests/e2e/__init__.py +0 -0
- rfnry_engine-0.1.0/tests/e2e/conftest.py +13 -0
- rfnry_engine-0.1.0/tests/e2e/test_live.py +112 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/billing/AGENT.md +6 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/frontdesk/AGENT.md +9 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/frontdesk/questions/intent.md +8 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/judge/AGENT.md +5 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/judge/questions/done.md +5 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/judge/questions/grounded.md +4 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/support/AGENT.md +7 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/support/knowledge/plans.md +4 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/support/rules/refunds.md +4 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/support/skills/escalate.md +5 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/support/tasks/triage.md +1 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/support/tools/lookup_order.md +8 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/support/tools/now.md +3 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/teacher/AGENT.md +6 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/triage/AGENT.md +5 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/triage/questions/severity.md +9 -0
- rfnry_engine-0.1.0/tests/fixtures/agents/triage/questions/urgent.md +4 -0
- rfnry_engine-0.1.0/tests/integration/__init__.py +0 -0
- rfnry_engine-0.1.0/tests/integration/test_storage.py +880 -0
- rfnry_engine-0.1.0/tests/integration/test_tenancy.py +112 -0
- rfnry_engine-0.1.0/tests/unit/__init__.py +0 -0
- rfnry_engine-0.1.0/tests/unit/conftest.py +10 -0
- rfnry_engine-0.1.0/tests/unit/fakes.py +283 -0
- rfnry_engine-0.1.0/tests/unit/lookalike.py +125 -0
- rfnry_engine-0.1.0/tests/unit/test_adapters.py +237 -0
- rfnry_engine-0.1.0/tests/unit/test_api.py +291 -0
- rfnry_engine-0.1.0/tests/unit/test_approve.py +244 -0
- rfnry_engine-0.1.0/tests/unit/test_builtins.py +490 -0
- rfnry_engine-0.1.0/tests/unit/test_bundle.py +51 -0
- rfnry_engine-0.1.0/tests/unit/test_chain.py +8 -0
- rfnry_engine-0.1.0/tests/unit/test_declared.py +78 -0
- rfnry_engine-0.1.0/tests/unit/test_docs.py +164 -0
- rfnry_engine-0.1.0/tests/unit/test_docs_examples.py +399 -0
- rfnry_engine-0.1.0/tests/unit/test_engine_decision.py +421 -0
- rfnry_engine-0.1.0/tests/unit/test_engine_generation.py +706 -0
- rfnry_engine-0.1.0/tests/unit/test_engine_nesting.py +438 -0
- rfnry_engine-0.1.0/tests/unit/test_engine_questions.py +389 -0
- rfnry_engine-0.1.0/tests/unit/test_engine_steering.py +527 -0
- rfnry_engine-0.1.0/tests/unit/test_engine_tool.py +229 -0
- rfnry_engine-0.1.0/tests/unit/test_engine_tools.py +82 -0
- rfnry_engine-0.1.0/tests/unit/test_engine_workspace.py +339 -0
- rfnry_engine-0.1.0/tests/unit/test_errors.py +26 -0
- rfnry_engine-0.1.0/tests/unit/test_events.py +175 -0
- rfnry_engine-0.1.0/tests/unit/test_examples.py +15 -0
- rfnry_engine-0.1.0/tests/unit/test_export.py +495 -0
- rfnry_engine-0.1.0/tests/unit/test_failed_runs.py +375 -0
- rfnry_engine-0.1.0/tests/unit/test_family.py +77 -0
- rfnry_engine-0.1.0/tests/unit/test_fingerprint.py +398 -0
- rfnry_engine-0.1.0/tests/unit/test_folders.py +167 -0
- rfnry_engine-0.1.0/tests/unit/test_history.py +619 -0
- rfnry_engine-0.1.0/tests/unit/test_label.py +445 -0
- rfnry_engine-0.1.0/tests/unit/test_local.py +675 -0
- rfnry_engine-0.1.0/tests/unit/test_messages.py +96 -0
- rfnry_engine-0.1.0/tests/unit/test_model_shapes.py +207 -0
- rfnry_engine-0.1.0/tests/unit/test_models.py +221 -0
- rfnry_engine-0.1.0/tests/unit/test_plain.py +117 -0
- rfnry_engine-0.1.0/tests/unit/test_preload.py +487 -0
- rfnry_engine-0.1.0/tests/unit/test_public_api.py +88 -0
- rfnry_engine-0.1.0/tests/unit/test_reasoning.py +261 -0
- rfnry_engine-0.1.0/tests/unit/test_refinery.py +340 -0
- rfnry_engine-0.1.0/tests/unit/test_replay.py +1084 -0
- rfnry_engine-0.1.0/tests/unit/test_tags.py +213 -0
- rfnry_engine-0.1.0/tests/unit/test_tools.py +74 -0
- rfnry_engine-0.1.0/tests/unit/test_tree.py +582 -0
- rfnry_engine-0.1.0/tests/unit/test_unicode_results.py +29 -0
- rfnry_engine-0.1.0/tests/unit/test_workspace.py +802 -0
- rfnry_engine-0.1.0/uv.lock +805 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: rfnry-engine
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent harness with built-in dataset generation
|
|
5
|
+
Author-email: rfnry <contact@rfnry.dev>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.12
|
|
8
|
+
Requires-Dist: pydantic>=2.13.4
|
|
9
|
+
Requires-Dist: pyyaml>=6.0.3
|
|
10
|
+
Requires-Dist: regex>=2026.9.10
|
|
11
|
+
Provides-Extra: dev
|
|
12
|
+
Requires-Dist: asyncpg<1.0,>=0.31.0; extra == 'dev'
|
|
13
|
+
Requires-Dist: httpx>=0.28.1; extra == 'dev'
|
|
14
|
+
Requires-Dist: mypy>=2.3.1; extra == 'dev'
|
|
15
|
+
Requires-Dist: poethepoet>=0.48.0; extra == 'dev'
|
|
16
|
+
Requires-Dist: pytest-asyncio>=1.4.0; extra == 'dev'
|
|
17
|
+
Requires-Dist: pytest>=9.1.1; extra == 'dev'
|
|
18
|
+
Requires-Dist: ruff>=0.16.4; extra == 'dev'
|
|
19
|
+
Requires-Dist: types-pyyaml>=6.0.12; extra == 'dev'
|
|
20
|
+
Requires-Dist: types-regex>=2026.9.10.20260911; extra == 'dev'
|
|
21
|
+
Provides-Extra: postgres
|
|
22
|
+
Requires-Dist: asyncpg<1.0,>=0.31.0; extra == 'postgres'
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# rfnry-engine
|
|
26
|
+
|
|
27
|
+
Agent harness with built-in dataset generation.
|
|
28
|
+
|
|
29
|
+
[`docs/engine.md`](https://github.com/rfnry/engine/blob/main/docs/engine.md).
|
|
30
|
+
[`docs/api.md`](https://github.com/rfnry/engine/blob/main/docs/api.md).
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "rfnry-engine"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Agent harness with built-in dataset generation"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.12"
|
|
8
|
+
authors = [{name = "rfnry", email = "contact@rfnry.dev"}]
|
|
9
|
+
dependencies = [
|
|
10
|
+
"pydantic>=2.13.4",
|
|
11
|
+
"pyyaml>=6.0.3",
|
|
12
|
+
"regex>=2026.9.10",
|
|
13
|
+
]
|
|
14
|
+
|
|
15
|
+
[project.optional-dependencies]
|
|
16
|
+
postgres = ["asyncpg>=0.31.0,<1.0"]
|
|
17
|
+
dev = [
|
|
18
|
+
"pytest>=9.1.1",
|
|
19
|
+
"pytest-asyncio>=1.4.0",
|
|
20
|
+
"ruff>=0.16.4",
|
|
21
|
+
"mypy>=2.3.1",
|
|
22
|
+
"poethepoet>=0.48.0",
|
|
23
|
+
"asyncpg>=0.31.0,<1.0",
|
|
24
|
+
"types-PyYAML>=6.0.12",
|
|
25
|
+
"types-regex>=2026.9.10.20260911",
|
|
26
|
+
"httpx>=0.28.1",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[build-system]
|
|
30
|
+
requires = ["hatchling"]
|
|
31
|
+
build-backend = "hatchling.build"
|
|
32
|
+
|
|
33
|
+
[tool.hatch.build.targets.wheel]
|
|
34
|
+
packages = ["src/rfnry_engine"]
|
|
35
|
+
|
|
36
|
+
[tool.pytest.ini_options]
|
|
37
|
+
asyncio_mode = "auto"
|
|
38
|
+
testpaths = ["tests"]
|
|
39
|
+
pythonpath = ["src", "tests"]
|
|
40
|
+
markers = [
|
|
41
|
+
"live: calls real models (set RFNRY_LIVE_ADAPTERS to a module path)",
|
|
42
|
+
]
|
|
43
|
+
addopts = "-m 'not live'"
|
|
44
|
+
asyncio_default_fixture_loop_scope = "session"
|
|
45
|
+
asyncio_default_test_loop_scope = "session"
|
|
46
|
+
|
|
47
|
+
[tool.ruff]
|
|
48
|
+
line-length = 100
|
|
49
|
+
target-version = "py312"
|
|
50
|
+
|
|
51
|
+
[tool.ruff.lint]
|
|
52
|
+
select = ["E", "F", "I", "W", "B", "UP", "SIM"]
|
|
53
|
+
ignore = ["E501"]
|
|
54
|
+
|
|
55
|
+
[tool.ruff.lint.isort]
|
|
56
|
+
combine-as-imports = true
|
|
57
|
+
|
|
58
|
+
[tool.poe.tasks]
|
|
59
|
+
format = { cmd = "ruff format src tests" }
|
|
60
|
+
check = { cmd = "ruff check src tests" }
|
|
61
|
+
"check:fix" = { cmd = "ruff check --fix src tests" }
|
|
62
|
+
typecheck = { cmd = "mypy" }
|
|
63
|
+
test = { cmd = "pytest -q" }
|
|
64
|
+
"test:e2e" = { cmd = "pytest -q -m live" }
|
|
65
|
+
|
|
66
|
+
[tool.mypy]
|
|
67
|
+
strict = true
|
|
68
|
+
files = ["src/rfnry_engine", "tests"]
|
|
69
|
+
|
|
70
|
+
[[tool.mypy.overrides]]
|
|
71
|
+
module = "asyncpg.*"
|
|
72
|
+
ignore_missing_imports = true
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
from rfnry_engine.engine import AgentInfo, Engine
|
|
2
|
+
from rfnry_engine.errors import (
|
|
3
|
+
ConfigurationError,
|
|
4
|
+
DelegationDepthError,
|
|
5
|
+
DelegationLoopError,
|
|
6
|
+
EngineException,
|
|
7
|
+
InputError,
|
|
8
|
+
ModelError,
|
|
9
|
+
NotFoundError,
|
|
10
|
+
RunawayError,
|
|
11
|
+
StorageError,
|
|
12
|
+
)
|
|
13
|
+
from rfnry_engine.jail import Jail
|
|
14
|
+
from rfnry_engine.models import (
|
|
15
|
+
Answer,
|
|
16
|
+
AnswerLike,
|
|
17
|
+
Audio,
|
|
18
|
+
Check,
|
|
19
|
+
CheckAnswer,
|
|
20
|
+
Choice,
|
|
21
|
+
ChoiceAnswer,
|
|
22
|
+
Content,
|
|
23
|
+
Decided,
|
|
24
|
+
DecidedLike,
|
|
25
|
+
DecisionModel,
|
|
26
|
+
Document,
|
|
27
|
+
GenerationModel,
|
|
28
|
+
HistoryLike,
|
|
29
|
+
Image,
|
|
30
|
+
Message,
|
|
31
|
+
Part,
|
|
32
|
+
Question,
|
|
33
|
+
QuestionLike,
|
|
34
|
+
Reply,
|
|
35
|
+
ReplyLike,
|
|
36
|
+
Score,
|
|
37
|
+
ScoreAnswer,
|
|
38
|
+
State,
|
|
39
|
+
StreamingGenerationModel,
|
|
40
|
+
Text,
|
|
41
|
+
ToolCall,
|
|
42
|
+
ToolSpec,
|
|
43
|
+
Usage,
|
|
44
|
+
confidence,
|
|
45
|
+
)
|
|
46
|
+
from rfnry_engine.records import (
|
|
47
|
+
Decision,
|
|
48
|
+
Event,
|
|
49
|
+
Fingerprint,
|
|
50
|
+
Generation,
|
|
51
|
+
Run,
|
|
52
|
+
Step,
|
|
53
|
+
TextDelta,
|
|
54
|
+
ToolCallStarted,
|
|
55
|
+
ToolResultReady,
|
|
56
|
+
)
|
|
57
|
+
from rfnry_engine.refinery.export import Bucket
|
|
58
|
+
from rfnry_engine.refinery.holdout import is_holdout
|
|
59
|
+
from rfnry_engine.refinery.labels import (
|
|
60
|
+
Conflict,
|
|
61
|
+
Exported,
|
|
62
|
+
Label,
|
|
63
|
+
Labeled,
|
|
64
|
+
Outcome,
|
|
65
|
+
Replay,
|
|
66
|
+
Review,
|
|
67
|
+
)
|
|
68
|
+
from rfnry_engine.refinery.postgres import PostgresStorage
|
|
69
|
+
from rfnry_engine.refinery.refinery import Refinery
|
|
70
|
+
from rfnry_engine.refinery.storage import LabelRow, RunRow, Storage
|
|
71
|
+
from rfnry_engine.stop import Stop
|
|
72
|
+
from rfnry_engine.tools.engine_tools import EngineTools
|
|
73
|
+
from rfnry_engine.tools.tools import Tool, Tools
|
|
74
|
+
from rfnry_engine.tools.workspace import Backend, Local, Process, Workspace
|
|
75
|
+
|
|
76
|
+
__version__ = "0.1.0"
|
|
77
|
+
|
|
78
|
+
__all__ = [
|
|
79
|
+
"AgentInfo",
|
|
80
|
+
"Answer",
|
|
81
|
+
"AnswerLike",
|
|
82
|
+
"Bucket",
|
|
83
|
+
"Check",
|
|
84
|
+
"CheckAnswer",
|
|
85
|
+
"Choice",
|
|
86
|
+
"ChoiceAnswer",
|
|
87
|
+
"ConfigurationError",
|
|
88
|
+
"Conflict",
|
|
89
|
+
"Decided",
|
|
90
|
+
"DecidedLike",
|
|
91
|
+
"Decision",
|
|
92
|
+
"DecisionModel",
|
|
93
|
+
"DelegationDepthError",
|
|
94
|
+
"DelegationLoopError",
|
|
95
|
+
"Engine",
|
|
96
|
+
"EngineException",
|
|
97
|
+
"EngineTools",
|
|
98
|
+
"Event",
|
|
99
|
+
"Exported",
|
|
100
|
+
"Fingerprint",
|
|
101
|
+
"Generation",
|
|
102
|
+
"GenerationModel",
|
|
103
|
+
"HistoryLike",
|
|
104
|
+
"InputError",
|
|
105
|
+
"Jail",
|
|
106
|
+
"Label",
|
|
107
|
+
"LabelRow",
|
|
108
|
+
"Labeled",
|
|
109
|
+
"Message",
|
|
110
|
+
"ModelError",
|
|
111
|
+
"NotFoundError",
|
|
112
|
+
"Outcome",
|
|
113
|
+
"PostgresStorage",
|
|
114
|
+
"Question",
|
|
115
|
+
"QuestionLike",
|
|
116
|
+
"Refinery",
|
|
117
|
+
"Replay",
|
|
118
|
+
"Reply",
|
|
119
|
+
"ReplyLike",
|
|
120
|
+
"Review",
|
|
121
|
+
"Run",
|
|
122
|
+
"RunRow",
|
|
123
|
+
"RunawayError",
|
|
124
|
+
"Score",
|
|
125
|
+
"ScoreAnswer",
|
|
126
|
+
"State",
|
|
127
|
+
"Step",
|
|
128
|
+
"Stop",
|
|
129
|
+
"Storage",
|
|
130
|
+
"StorageError",
|
|
131
|
+
"StreamingGenerationModel",
|
|
132
|
+
"TextDelta",
|
|
133
|
+
"Tool",
|
|
134
|
+
"ToolCall",
|
|
135
|
+
"ToolCallStarted",
|
|
136
|
+
"ToolResultReady",
|
|
137
|
+
"ToolSpec",
|
|
138
|
+
"Tools",
|
|
139
|
+
"Usage",
|
|
140
|
+
"Workspace",
|
|
141
|
+
"Backend",
|
|
142
|
+
"Local",
|
|
143
|
+
"Process",
|
|
144
|
+
"confidence",
|
|
145
|
+
"is_holdout",
|
|
146
|
+
"Text",
|
|
147
|
+
"Image",
|
|
148
|
+
"Audio",
|
|
149
|
+
"Document",
|
|
150
|
+
"Part",
|
|
151
|
+
"Content",
|
|
152
|
+
]
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from rfnry_engine.models import ToolSpec
|
|
6
|
+
from rfnry_engine.tree import AgentSpec
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def _read(path: Path) -> str:
|
|
10
|
+
return path.read_text(encoding="utf-8").strip() if path.is_file() else ""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def build_bundle(
|
|
14
|
+
spec: AgentSpec,
|
|
15
|
+
*,
|
|
16
|
+
task: str | None,
|
|
17
|
+
tools: list[ToolSpec],
|
|
18
|
+
agents: list[AgentSpec],
|
|
19
|
+
context: str | None,
|
|
20
|
+
) -> str:
|
|
21
|
+
sections = [
|
|
22
|
+
("AGENT", spec.body),
|
|
23
|
+
("TASK", "" if task is None else _read(spec.root / "tasks" / f"{task}.md")),
|
|
24
|
+
("FILES", spec.listing),
|
|
25
|
+
(
|
|
26
|
+
"TOOLS",
|
|
27
|
+
"\n".join(
|
|
28
|
+
f"- {t.name}: {t.description.splitlines()[0] if t.description else ''}"
|
|
29
|
+
for t in tools
|
|
30
|
+
),
|
|
31
|
+
),
|
|
32
|
+
("AGENTS", "\n".join(f"- {a.name}: {a.description}" for a in agents)),
|
|
33
|
+
("CONTEXT", (context or "").strip()),
|
|
34
|
+
]
|
|
35
|
+
return "\n\n".join(f"<<<{label}>>>\n{body}" for label, body in sections if body)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import hashlib
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
from rfnry_engine.models import Message, Part, ToolSpec
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def opening(model: str | None, system: str, tools: list[ToolSpec], preload: list[Part]) -> bytes:
|
|
10
|
+
head = {
|
|
11
|
+
"model": model,
|
|
12
|
+
"system": system,
|
|
13
|
+
"tools": [tool.model_dump(mode="json") for tool in tools],
|
|
14
|
+
"preload": [part.model_dump(mode="json") for part in preload],
|
|
15
|
+
}
|
|
16
|
+
return hashlib.sha256(json.dumps(head, sort_keys=True, ensure_ascii=False).encode()).digest()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def extend(digest: bytes, message: Message) -> bytes:
|
|
20
|
+
said = message.model_dump(mode="json", exclude={"prefix", "usage"}, fallback=repr)
|
|
21
|
+
return hashlib.sha256(
|
|
22
|
+
digest + json.dumps(said, sort_keys=True, ensure_ascii=False).encode()
|
|
23
|
+
).digest()
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def mark(digest: bytes) -> str:
|
|
27
|
+
return digest.hex()[:16]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def settle(history: list[Message], digest: bytes) -> tuple[list[Message], bytes]:
|
|
31
|
+
sent: list[Message] = []
|
|
32
|
+
for message in history:
|
|
33
|
+
if message.reasoning is not None and message.prefix != mark(digest):
|
|
34
|
+
message = message.model_copy(update={"reasoning": None})
|
|
35
|
+
sent.append(message)
|
|
36
|
+
digest = extend(digest, message)
|
|
37
|
+
return sent, digest
|