maf-cachebench 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.
Files changed (29) hide show
  1. maf_cachebench-0.1.0/LICENSE +21 -0
  2. maf_cachebench-0.1.0/PKG-INFO +125 -0
  3. maf_cachebench-0.1.0/README.md +86 -0
  4. maf_cachebench-0.1.0/pyproject.toml +116 -0
  5. maf_cachebench-0.1.0/pyproject.toml.orig +94 -0
  6. maf_cachebench-0.1.0/src/maf_cachebench/__init__.py +236 -0
  7. maf_cachebench-0.1.0/src/maf_cachebench/__main__.py +8 -0
  8. maf_cachebench-0.1.0/src/maf_cachebench/_advise_cli.py +303 -0
  9. maf_cachebench-0.1.0/src/maf_cachebench/_advisor.py +461 -0
  10. maf_cachebench-0.1.0/src/maf_cachebench/_cli.py +456 -0
  11. maf_cachebench-0.1.0/src/maf_cachebench/_cli_selection.py +194 -0
  12. maf_cachebench-0.1.0/src/maf_cachebench/_fill.py +454 -0
  13. maf_cachebench-0.1.0/src/maf_cachebench/_live.py +2319 -0
  14. maf_cachebench-0.1.0/src/maf_cachebench/_live_cli.py +4371 -0
  15. maf_cachebench-0.1.0/src/maf_cachebench/_metrics.py +176 -0
  16. maf_cachebench-0.1.0/src/maf_cachebench/_providers.py +424 -0
  17. maf_cachebench-0.1.0/src/maf_cachebench/_recall.py +767 -0
  18. maf_cachebench-0.1.0/src/maf_cachebench/_recall_cli.py +238 -0
  19. maf_cachebench-0.1.0/src/maf_cachebench/_records.py +1771 -0
  20. maf_cachebench-0.1.0/src/maf_cachebench/_report.py +162 -0
  21. maf_cachebench-0.1.0/src/maf_cachebench/_run_identity.py +9 -0
  22. maf_cachebench-0.1.0/src/maf_cachebench/_runner.py +529 -0
  23. maf_cachebench-0.1.0/src/maf_cachebench/_strategies.py +745 -0
  24. maf_cachebench-0.1.0/src/maf_cachebench/_summary.py +335 -0
  25. maf_cachebench-0.1.0/src/maf_cachebench/_summary_cli.py +341 -0
  26. maf_cachebench-0.1.0/src/maf_cachebench/_tokenizers.py +200 -0
  27. maf_cachebench-0.1.0/src/maf_cachebench/_transcripts.py +387 -0
  28. maf_cachebench-0.1.0/src/maf_cachebench/_types.py +200 -0
  29. maf_cachebench-0.1.0/src/maf_cachebench/py.typed +0 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 SOKOLAI BV
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,125 @@
1
+ Metadata-Version: 2.4
2
+ Name: maf-cachebench
3
+ Version: 0.1.0
4
+ Summary: Benchmark for Microsoft Agent Framework compaction strategies: what each one costs in prompt-cache hits and what it loses, measured on a replayed conversation or a live agent.
5
+ Keywords: benchmark,compaction,prompt-cache,agent-framework,microsoft-agent-framework
6
+ Author: SOKOLAI BV
7
+ Author-email: SOKOLAI BV <info@sokolai.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Typing :: Typed
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Classifier: Topic :: Software Development :: Testing
18
+ Requires-Dist: agent-framework-core>=1.20.0,<1.21
19
+ Requires-Dist: maf-compaction>=0.0.0,<0.2
20
+ Requires-Dist: httpx>=0.27,<1
21
+ Requires-Dist: agent-framework-foundry>=1.14.0,<2 ; extra == 'foundry'
22
+ Requires-Dist: azure-identity>=1.17,<2 ; extra == 'foundry'
23
+ Requires-Dist: agent-framework-mistral>=1.0.0b260918,<2 ; extra == 'mistral'
24
+ Requires-Dist: agent-framework-ollama>=1.0.0b261002,<2 ; extra == 'ollama'
25
+ Requires-Dist: ollama>=0.5.3,<0.6 ; extra == 'ollama'
26
+ Requires-Dist: agent-framework-openai>=1.15.0,<2 ; extra == 'openai'
27
+ Requires-Dist: azure-identity>=1.17,<2 ; extra == 'openai'
28
+ Requires-Dist: tiktoken>=0.8,<1 ; extra == 'tiktoken'
29
+ Requires-Python: >=3.12, <3.15
30
+ Project-URL: Homepage, https://www.sokol.ai
31
+ Project-URL: Source, https://github.com/sokolaidev/maf-extensions
32
+ Project-URL: Issues, https://github.com/sokolaidev/maf-extensions/issues
33
+ Provides-Extra: foundry
34
+ Provides-Extra: mistral
35
+ Provides-Extra: ollama
36
+ Provides-Extra: openai
37
+ Provides-Extra: tiktoken
38
+ Description-Content-Type: text/markdown
39
+
40
+ # maf-cachebench
41
+
42
+ [![PyPI](https://img.shields.io/pypi/v/maf-cachebench)](https://pypi.org/project/maf-cachebench/) [![Python](https://img.shields.io/pypi/pyversions/maf-cachebench)](https://pypi.org/project/maf-cachebench/) [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/sokolaidev/maf-extensions/blob/main/LICENSE)
43
+
44
+ > **Experimental.** Releases before 1.0 may change or remove APIs. Importing this package emits `MafCachebenchExperimentalWarning`.
45
+
46
+ A benchmark for [Microsoft Agent Framework](https://aka.ms/AgentFramework) compaction strategies. It measures what each strategy costs in provider prompt-cache hits and what it destroys while doing it: the framework's own strategies and the ones in [`maf-compaction`](https://pypi.org/project/maf-compaction/), twenty in all, on the same conversation, priced at the provider's cached and uncached rates.
47
+
48
+ This is an independent package. It is not affiliated with or endorsed by Microsoft.
49
+
50
+ ## What it measures
51
+
52
+ Provider prompt caches match on an exact prefix, and every compaction strategy rewrites history inside that prefix, so compaction breaks the cache by construction. The question is what that costs and what it saves. The benchmark plants facts in a conversation's tool results, runs the conversation under each strategy, and then asks for the facts back from the compacted context. Each strategy gets a cost, a cache hit rate, and a count of facts it kept, and the strategies are ranked on cost behind an accuracy bar.
53
+
54
+ Two harnesses answer two questions:
55
+
56
+ - `cachebench` replays a scripted transcript. Every provider and strategy replays the same scripted structure, with a fixed-width per-cell salt isolating its cache prefix. These controlled workloads support comparisons across providers. It answers how a *prompt* caches.
57
+ - `cachebench_live` drives a real agent, with real replies and real tool calls, so compaction acts on the history an agent would accumulate. Its numbers compare strategies within one model. It answers what a strategy costs an agent.
58
+
59
+ ## Install
60
+
61
+ ```bash
62
+ pip install maf-cachebench[openai] # Azure OpenAI, OpenRouter and the Azure Responses route
63
+ pip install maf-cachebench[foundry] # Microsoft Foundry project endpoints
64
+ pip install maf-cachebench[mistral]
65
+ pip install maf-cachebench[ollama]
66
+ pip install maf-cachebench[tiktoken] # then pass --tokenizer tiktoken for exact counts
67
+ ```
68
+
69
+ Five commands land on the path: `cachebench`, `cachebench_live`, `cachebench_advise`, `cachebench_recall` and `cachebench_summary`. Each prints its own `--help`.
70
+
71
+ ## Pointing it at a provider
72
+
73
+ A provider is selected as `provider` or `provider:model`, so two models on one provider can sit in one run. Credentials come from the environment:
74
+
75
+ | Provider | Variables |
76
+ |---|---|
77
+ | `azure` | `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_CHAT_COMPLETION_MODEL` |
78
+ | `azure-responses` | `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_RESPONSES_MODEL`, and a working `DefaultAzureCredential` (Entra authentication) |
79
+ | `foundry` | `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL`, and a working `DefaultAzureCredential` |
80
+ | `openrouter` | `OPENROUTER_API_KEY`, `OPENROUTER_MODEL`; pin `OPENROUTER_PROVIDER_ORDER`, or you measure the router |
81
+ | `mistral` | `MISTRAL_API_KEY`, `MISTRAL_CHAT_MODEL` |
82
+ | `ollama` | `OLLAMA_MODEL`, optionally `OLLAMA_HOST` and `OLLAMA_API_KEY`; this provider caches and never reports it |
83
+
84
+ Prices are passed as `--price-input`, `--price-cached` and `--price-output` per million tokens, except on OpenRouter where they are discovered.
85
+
86
+ ## A first run
87
+
88
+ ```bash
89
+ # see the plan and the prompt sizes without spending anything
90
+ cachebench_live foundry:gpt-5.6-luna --dry-run --strategies none,anchored,tool_summary_anchored
91
+
92
+ # a live cell: a 120K window, a conversation sized to 1.5 times it, five seeds, records kept
93
+ cachebench_live foundry:gpt-5.6-luna --agent harness --context-window 120000 --fill 1.5 --repeats 5 \
94
+ --strategies none,tool_summary_anchored,tool_and_user_summary_anchored --summarizer-provider foundry:gpt-5.6-luna \
95
+ --price-input 0.20 --price-cached 0.02 --price-output 1.20 --results-jsonl runs/cell.jsonl
96
+
97
+ # render the table again from the records, calling nothing
98
+ cachebench_live --from-jsonl runs/cell.jsonl
99
+ ```
100
+
101
+ `--results-jsonl` is not optional in practice: a cell runs for hours, and the file is appended one record per seed the moment it is scored, so a stopped run keeps what it paid for. `--from-jsonl` rebuilds the table and the verdict from the records with no provider configured, and files that measured the same cell merge into one table.
102
+
103
+ ## Reading the table
104
+
105
+ Rows that keep at least `--min-correctness` of the control's accuracy come first, cheapest first; the rest follow below a line. The columns to read, in order:
106
+
107
+ - `seed$` is what the conversation cost, the strategy's own summariser calls included and the probes left out. The ranking and the verdict are on it.
108
+ - `seed$+-` is the spread between the cheapest and dearest seed. A gap smaller than this is not a result, and the verdict line says `NOT SUPPORTED` when that happens.
109
+ - `seed hit%` is the share of the conversation's input the provider served from its cache, probes excluded. This is what compaction did to the cache.
110
+ - `facts` and `lost` are the planted facts that survived into the compacted context, and the ones compaction removed.
111
+ - `acc1` and `acc2` are the model's accuracy on the scoped and the combined closing questions, asked from the same restored snapshot.
112
+ - `dq` marks a row that sent a prompt larger than the window it stood in for. A cell that disqualifies at all leaves the ranking.
113
+ - `flags` is where a row says it is not measuring what its name claims. Read it before the money columns.
114
+
115
+ The full option reference, the flags legend and the measurement design are in the [benchmark documentation](https://github.com/sokolaidev/maf-extensions/blob/main/docs/compaction/cachebench.md), and the findings in the [compaction overview](https://github.com/sokolaidev/maf-extensions/blob/main/docs/compaction/README.md).
116
+
117
+ ## What it depends on
118
+
119
+ `agent-framework-core`, pinned to one minor because the benchmark reads the framework's private `_compaction` helpers; `maf-compaction` for the strategies it was built to measure; and `httpx` for the advisor's price lookup. The provider clients are extras.
120
+
121
+ ## Licence
122
+
123
+ MIT. See the [LICENSE](https://github.com/sokolaidev/maf-extensions/blob/main/LICENSE).
124
+
125
+ All commands default to the dependency-free estimator. Install the `tiktoken` extra and pass `--tokenizer tiktoken` for BPE counts. Explicit price rates must be finite and non-negative.
@@ -0,0 +1,86 @@
1
+ # maf-cachebench
2
+
3
+ [![PyPI](https://img.shields.io/pypi/v/maf-cachebench)](https://pypi.org/project/maf-cachebench/) [![Python](https://img.shields.io/pypi/pyversions/maf-cachebench)](https://pypi.org/project/maf-cachebench/) [![License](https://img.shields.io/badge/license-MIT-green)](https://github.com/sokolaidev/maf-extensions/blob/main/LICENSE)
4
+
5
+ > **Experimental.** Releases before 1.0 may change or remove APIs. Importing this package emits `MafCachebenchExperimentalWarning`.
6
+
7
+ A benchmark for [Microsoft Agent Framework](https://aka.ms/AgentFramework) compaction strategies. It measures what each strategy costs in provider prompt-cache hits and what it destroys while doing it: the framework's own strategies and the ones in [`maf-compaction`](https://pypi.org/project/maf-compaction/), twenty in all, on the same conversation, priced at the provider's cached and uncached rates.
8
+
9
+ This is an independent package. It is not affiliated with or endorsed by Microsoft.
10
+
11
+ ## What it measures
12
+
13
+ Provider prompt caches match on an exact prefix, and every compaction strategy rewrites history inside that prefix, so compaction breaks the cache by construction. The question is what that costs and what it saves. The benchmark plants facts in a conversation's tool results, runs the conversation under each strategy, and then asks for the facts back from the compacted context. Each strategy gets a cost, a cache hit rate, and a count of facts it kept, and the strategies are ranked on cost behind an accuracy bar.
14
+
15
+ Two harnesses answer two questions:
16
+
17
+ - `cachebench` replays a scripted transcript. Every provider and strategy replays the same scripted structure, with a fixed-width per-cell salt isolating its cache prefix. These controlled workloads support comparisons across providers. It answers how a *prompt* caches.
18
+ - `cachebench_live` drives a real agent, with real replies and real tool calls, so compaction acts on the history an agent would accumulate. Its numbers compare strategies within one model. It answers what a strategy costs an agent.
19
+
20
+ ## Install
21
+
22
+ ```bash
23
+ pip install maf-cachebench[openai] # Azure OpenAI, OpenRouter and the Azure Responses route
24
+ pip install maf-cachebench[foundry] # Microsoft Foundry project endpoints
25
+ pip install maf-cachebench[mistral]
26
+ pip install maf-cachebench[ollama]
27
+ pip install maf-cachebench[tiktoken] # then pass --tokenizer tiktoken for exact counts
28
+ ```
29
+
30
+ Five commands land on the path: `cachebench`, `cachebench_live`, `cachebench_advise`, `cachebench_recall` and `cachebench_summary`. Each prints its own `--help`.
31
+
32
+ ## Pointing it at a provider
33
+
34
+ A provider is selected as `provider` or `provider:model`, so two models on one provider can sit in one run. Credentials come from the environment:
35
+
36
+ | Provider | Variables |
37
+ |---|---|
38
+ | `azure` | `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_API_KEY`, `AZURE_OPENAI_CHAT_COMPLETION_MODEL` |
39
+ | `azure-responses` | `AZURE_OPENAI_ENDPOINT`, `AZURE_OPENAI_RESPONSES_MODEL`, and a working `DefaultAzureCredential` (Entra authentication) |
40
+ | `foundry` | `FOUNDRY_PROJECT_ENDPOINT`, `FOUNDRY_MODEL`, and a working `DefaultAzureCredential` |
41
+ | `openrouter` | `OPENROUTER_API_KEY`, `OPENROUTER_MODEL`; pin `OPENROUTER_PROVIDER_ORDER`, or you measure the router |
42
+ | `mistral` | `MISTRAL_API_KEY`, `MISTRAL_CHAT_MODEL` |
43
+ | `ollama` | `OLLAMA_MODEL`, optionally `OLLAMA_HOST` and `OLLAMA_API_KEY`; this provider caches and never reports it |
44
+
45
+ Prices are passed as `--price-input`, `--price-cached` and `--price-output` per million tokens, except on OpenRouter where they are discovered.
46
+
47
+ ## A first run
48
+
49
+ ```bash
50
+ # see the plan and the prompt sizes without spending anything
51
+ cachebench_live foundry:gpt-5.6-luna --dry-run --strategies none,anchored,tool_summary_anchored
52
+
53
+ # a live cell: a 120K window, a conversation sized to 1.5 times it, five seeds, records kept
54
+ cachebench_live foundry:gpt-5.6-luna --agent harness --context-window 120000 --fill 1.5 --repeats 5 \
55
+ --strategies none,tool_summary_anchored,tool_and_user_summary_anchored --summarizer-provider foundry:gpt-5.6-luna \
56
+ --price-input 0.20 --price-cached 0.02 --price-output 1.20 --results-jsonl runs/cell.jsonl
57
+
58
+ # render the table again from the records, calling nothing
59
+ cachebench_live --from-jsonl runs/cell.jsonl
60
+ ```
61
+
62
+ `--results-jsonl` is not optional in practice: a cell runs for hours, and the file is appended one record per seed the moment it is scored, so a stopped run keeps what it paid for. `--from-jsonl` rebuilds the table and the verdict from the records with no provider configured, and files that measured the same cell merge into one table.
63
+
64
+ ## Reading the table
65
+
66
+ Rows that keep at least `--min-correctness` of the control's accuracy come first, cheapest first; the rest follow below a line. The columns to read, in order:
67
+
68
+ - `seed$` is what the conversation cost, the strategy's own summariser calls included and the probes left out. The ranking and the verdict are on it.
69
+ - `seed$+-` is the spread between the cheapest and dearest seed. A gap smaller than this is not a result, and the verdict line says `NOT SUPPORTED` when that happens.
70
+ - `seed hit%` is the share of the conversation's input the provider served from its cache, probes excluded. This is what compaction did to the cache.
71
+ - `facts` and `lost` are the planted facts that survived into the compacted context, and the ones compaction removed.
72
+ - `acc1` and `acc2` are the model's accuracy on the scoped and the combined closing questions, asked from the same restored snapshot.
73
+ - `dq` marks a row that sent a prompt larger than the window it stood in for. A cell that disqualifies at all leaves the ranking.
74
+ - `flags` is where a row says it is not measuring what its name claims. Read it before the money columns.
75
+
76
+ The full option reference, the flags legend and the measurement design are in the [benchmark documentation](https://github.com/sokolaidev/maf-extensions/blob/main/docs/compaction/cachebench.md), and the findings in the [compaction overview](https://github.com/sokolaidev/maf-extensions/blob/main/docs/compaction/README.md).
77
+
78
+ ## What it depends on
79
+
80
+ `agent-framework-core`, pinned to one minor because the benchmark reads the framework's private `_compaction` helpers; `maf-compaction` for the strategies it was built to measure; and `httpx` for the advisor's price lookup. The provider clients are extras.
81
+
82
+ ## Licence
83
+
84
+ MIT. See the [LICENSE](https://github.com/sokolaidev/maf-extensions/blob/main/LICENSE).
85
+
86
+ All commands default to the dependency-free estimator. Install the `tiktoken` extra and pass `--tokenizer tiktoken` for BPE counts. Explicit price rates must be finite and non-negative.
@@ -0,0 +1,116 @@
1
+ [project]
2
+ name = "maf-cachebench"
3
+ version = "0.1.0"
4
+ description = "Benchmark for Microsoft Agent Framework compaction strategies: what each one costs in prompt-cache hits and what it loses, measured on a replayed conversation or a live agent."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12,<3.15"
7
+ license = "MIT"
8
+ license-files = ["LICENSE"]
9
+ keywords = [
10
+ "benchmark",
11
+ "compaction",
12
+ "prompt-cache",
13
+ "agent-framework",
14
+ "microsoft-agent-framework",
15
+ ]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "Typing :: Typed",
20
+ "Programming Language :: Python :: 3",
21
+ "Programming Language :: Python :: 3.12",
22
+ "Programming Language :: Python :: 3.13",
23
+ "Programming Language :: Python :: 3.14",
24
+ "Topic :: Software Development :: Testing",
25
+ ]
26
+ dependencies = [
27
+ "agent-framework-core>=1.20.0,<1.21",
28
+ "maf-compaction>=0.0.0,<0.2",
29
+ "httpx>=0.27,<1",
30
+ ]
31
+
32
+ [[project.authors]]
33
+ name = "SOKOLAI BV"
34
+ email = "info@sokolai.com"
35
+
36
+ [project.optional-dependencies]
37
+ openai = [
38
+ "agent-framework-openai>=1.15.0,<2",
39
+ "azure-identity>=1.17,<2",
40
+ ]
41
+ foundry = [
42
+ "agent-framework-foundry>=1.14.0,<2",
43
+ "azure-identity>=1.17,<2",
44
+ ]
45
+ mistral = ["agent-framework-mistral>=1.0.0b260918,<2"]
46
+ ollama = [
47
+ "agent-framework-ollama>=1.0.0b261002,<2",
48
+ "ollama>=0.5.3,<0.6",
49
+ ]
50
+ tiktoken = ["tiktoken>=0.8,<1"]
51
+
52
+ [project.scripts]
53
+ cachebench = "maf_cachebench:main"
54
+ cachebench_live = "maf_cachebench:live_main"
55
+ cachebench_advise = "maf_cachebench:advise_main"
56
+ cachebench_recall = "maf_cachebench:recall_main"
57
+ cachebench_summary = "maf_cachebench:summary_main"
58
+
59
+ [project.urls]
60
+ Homepage = "https://www.sokol.ai"
61
+ Source = "https://github.com/sokolaidev/maf-extensions"
62
+ Issues = "https://github.com/sokolaidev/maf-extensions/issues"
63
+
64
+ [tool.uv.sources.maf-compaction]
65
+ workspace = true
66
+
67
+ [tool.uv.build-backend]
68
+ module-name = "maf_cachebench"
69
+ module-root = "src"
70
+
71
+ [tool.ruff]
72
+ line-length = 100
73
+ target-version = "py312"
74
+
75
+ [tool.ruff.lint]
76
+ extend-select = [
77
+ "I",
78
+ "UP",
79
+ "D100",
80
+ "D101",
81
+ "D103",
82
+ "D104",
83
+ "E501",
84
+ "W505",
85
+ "TD",
86
+ "FIX",
87
+ "ERA001",
88
+ ]
89
+
90
+ [tool.ruff.lint.per-file-ignores]
91
+ "tests/**" = [
92
+ "D101",
93
+ "D103",
94
+ "E501",
95
+ "W505",
96
+ ]
97
+ "samples/**" = [
98
+ "D101",
99
+ "D103",
100
+ "E501",
101
+ "W505",
102
+ ]
103
+
104
+ [tool.ruff.lint.pycodestyle]
105
+ max-doc-length = 100
106
+
107
+ [tool.pyright]
108
+ include = ["src"]
109
+ typeCheckingMode = "strict"
110
+
111
+ [tool.pytest.ini_options]
112
+ testpaths = ["tests"]
113
+
114
+ [build-system]
115
+ requires = ["uv_build>=0.11.24,<0.12.0"]
116
+ build-backend = "uv_build"
@@ -0,0 +1,94 @@
1
+ [project]
2
+ name = "maf-cachebench"
3
+ version = "0.1.0"
4
+ description = "Benchmark for Microsoft Agent Framework compaction strategies: what each one costs in prompt-cache hits and what it loses, measured on a replayed conversation or a live agent."
5
+ readme = "README.md"
6
+ requires-python = ">=3.12,<3.15"
7
+ authors = [{ name = "SOKOLAI BV", email = "info@sokolai.com" }]
8
+ license = "MIT"
9
+ license-files = ["LICENSE"]
10
+ keywords = [
11
+ "benchmark",
12
+ "compaction",
13
+ "prompt-cache",
14
+ "agent-framework",
15
+ "microsoft-agent-framework",
16
+ ]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Developers",
20
+ "Typing :: Typed",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.12",
23
+ "Programming Language :: Python :: 3.13",
24
+ "Programming Language :: Python :: 3.14",
25
+ "Topic :: Software Development :: Testing",
26
+ ]
27
+ dependencies = [
28
+ # The strategies under measurement, and the private `_compaction` helpers both packages
29
+ # read; the same minor as maf-compaction pins, for the same reason.
30
+ "agent-framework-core>=1.20.0,<1.21",
31
+ # Include the unreleased workspace version and the first release line.
32
+ "maf-compaction>=0.0.0,<0.2",
33
+ # Model prices are fetched from OpenRouter for the advisor's cost table.
34
+ "httpx>=0.27,<1",
35
+ ]
36
+
37
+ [project.optional-dependencies]
38
+ # One extra per provider route; a run needs only the client it points at. The floors are the
39
+ # client versions the published measurements were made with; the Mistral and Ollama clients
40
+ # have only pre-release versions on the index, so their floors name one.
41
+ openai = ["agent-framework-openai>=1.15.0,<2", "azure-identity>=1.17,<2"]
42
+ foundry = ["agent-framework-foundry>=1.14.0,<2", "azure-identity>=1.17,<2"]
43
+ mistral = ["agent-framework-mistral>=1.0.0b260918,<2"]
44
+ # The Ollama route builds the client's own async HTTP client, so that package is named too.
45
+ ollama = ["agent-framework-ollama>=1.0.0b261002,<2", "ollama>=0.5.3,<0.6"]
46
+ tiktoken = ["tiktoken>=0.8,<1"]
47
+
48
+ [project.scripts]
49
+ cachebench = "maf_cachebench:main"
50
+ cachebench_live = "maf_cachebench:live_main"
51
+ cachebench_advise = "maf_cachebench:advise_main"
52
+ cachebench_recall = "maf_cachebench:recall_main"
53
+ cachebench_summary = "maf_cachebench:summary_main"
54
+
55
+ [project.urls]
56
+ Homepage = "https://www.sokol.ai"
57
+ Source = "https://github.com/sokolaidev/maf-extensions"
58
+ Issues = "https://github.com/sokolaidev/maf-extensions/issues"
59
+
60
+ [tool.uv.sources]
61
+ maf-compaction = { workspace = true }
62
+
63
+ [tool.uv.build-backend]
64
+ module-name = "maf_cachebench"
65
+ module-root = "src"
66
+
67
+ [tool.ruff]
68
+ line-length = 100
69
+ target-version = "py312"
70
+
71
+ [tool.ruff.lint]
72
+ extend-select = ["I", "UP", "D100", "D101", "D103", "D104", "E501", "W505", "TD", "FIX", "ERA001"]
73
+
74
+ [tool.ruff.lint.per-file-ignores]
75
+ # ruff resolves [tool.ruff] per file by the NEAREST ancestor pyproject.toml that has one, so
76
+ # this package carries its own copy rather than inheriting one from the workspace root.
77
+ # The length rules join the docstring pair: test fixtures and probe scripts are lenient, not
78
+ # API surface.
79
+ "tests/**" = ["D101", "D103", "E501", "W505"]
80
+ "samples/**" = ["D101", "D103", "E501", "W505"]
81
+
82
+ [tool.ruff.lint.pycodestyle]
83
+ max-doc-length = 100
84
+
85
+ [tool.pyright]
86
+ include = ["src"]
87
+ typeCheckingMode = "strict"
88
+
89
+ [tool.pytest.ini_options]
90
+ testpaths = ["tests"]
91
+
92
+ [build-system]
93
+ requires = ["uv_build>=0.11.24,<0.12.0"]
94
+ build-backend = "uv_build"
@@ -0,0 +1,236 @@
1
+ """Compaction and prompt-cache interaction benchmark for Agent Framework.
2
+
3
+ Provider prompt caches match on exact prefixes, and every compaction strategy works by
4
+ excluding or rewriting messages inside an existing history. Compaction therefore breaks
5
+ the cached prefix by construction, and the question this package answers empirically is
6
+ what that costs: how much of each prompt stays reusable under a given strategy, how much
7
+ of that reusable prefix a given provider actually serves from cache, and whether
8
+ compacting earlier saves more prompt tokens than it loses in cache reads.
9
+
10
+ Measurements replay deterministic scripted content and structure across providers and
11
+ strategies, with fixed-width per-cell salts isolating their cache prefixes.
12
+ """
13
+
14
+ import importlib.metadata
15
+
16
+ from ._advise_cli import main as advise_main
17
+ from ._advisor import ModelPricing, StrategyCost, Verdict, advise, cost_of, fetch_openrouter_pricing
18
+ from ._cli import build_parser, main, run_benchmark
19
+ from ._fill import FillPlan, plan_fill
20
+ from ._live import (
21
+ AGENT_KINDS,
22
+ DEFAULT_COMBINED_REPEATS,
23
+ DEFAULT_PROBE_REPEATS,
24
+ IdentifiedHistoryProvider,
25
+ LiveOutcome,
26
+ MeteredClient,
27
+ ModelCall,
28
+ ProbeOutcome,
29
+ SummarizerUsage,
30
+ UsageRecorder,
31
+ build_live_agent,
32
+ build_live_scenario,
33
+ find_nested_strategy,
34
+ make_lookup_tool,
35
+ probe_count,
36
+ restore_state,
37
+ run_live,
38
+ score_combined_samples,
39
+ score_samples,
40
+ serialize_history,
41
+ snapshot_state,
42
+ unretrieved_facts,
43
+ wants_client_side_history,
44
+ )
45
+ from ._live_cli import main as live_main
46
+ from ._metrics import (
47
+ common_message_prefix,
48
+ percentile,
49
+ serialize_message,
50
+ summarize_cell,
51
+ token_counts,
52
+ )
53
+ from ._providers import (
54
+ PROVIDER_SPECS,
55
+ ProviderRuntime,
56
+ ProviderSpec,
57
+ build_provider,
58
+ parse_provider_selector,
59
+ prompt_cache_key,
60
+ prompt_cache_key_options,
61
+ provider_names,
62
+ )
63
+ from ._recall import (
64
+ Contradiction,
65
+ FactOutcome,
66
+ PlantedFact,
67
+ RecallScenario,
68
+ RecallScore,
69
+ build_recall_scenario,
70
+ score_answer,
71
+ )
72
+ from ._recall_cli import main as recall_main
73
+ from ._records import (
74
+ SCHEMA_VERSION,
75
+ CellParams,
76
+ SeedRecord,
77
+ StrategySettings,
78
+ WorkloadSettings,
79
+ append_seed_record,
80
+ group_by_cell,
81
+ read_seed_records,
82
+ )
83
+ from ._report import render_summary_table, write_records_jsonl, write_summary_csv
84
+ from ._runner import CallOutcome, ProviderCaller, TurnCaller, run_cell, unsupported_option
85
+ from ._strategies import (
86
+ STRATEGIES_FORCING_RECORDS,
87
+ STRATEGIES_NEEDING_SUMMARIZER,
88
+ STRATEGY_BUILDERS,
89
+ StrategyOptions,
90
+ build_strategy,
91
+ forces_records,
92
+ resolve_context_window,
93
+ strategy_names,
94
+ )
95
+ from ._summary import JointOutcome, JointVerdict, recommend, relative_correctness
96
+ from ._summary_cli import main as summary_main
97
+ from ._tokenizers import TOKENIZER_NAMES, build_tokenizer
98
+ from ._transcripts import (
99
+ DEFAULT_SYSTEM_TOKENS,
100
+ TRANSCRIPT_PRESETS,
101
+ TranscriptPreset,
102
+ build_preset,
103
+ build_transcript,
104
+ )
105
+ from ._types import CellKey, CellSummary, Transcript, TranscriptTurn, TurnRecord
106
+
107
+ try:
108
+ __version__ = importlib.metadata.version(__name__)
109
+ except importlib.metadata.PackageNotFoundError:
110
+ __version__ = "0.1.0" # Fallback for development mode
111
+
112
+ __all__ = [
113
+ "AGENT_KINDS",
114
+ "DEFAULT_COMBINED_REPEATS",
115
+ "DEFAULT_PROBE_REPEATS",
116
+ "DEFAULT_SYSTEM_TOKENS",
117
+ "PROVIDER_SPECS",
118
+ "SCHEMA_VERSION",
119
+ "STRATEGIES_FORCING_RECORDS",
120
+ "STRATEGIES_NEEDING_SUMMARIZER",
121
+ "STRATEGY_BUILDERS",
122
+ "TOKENIZER_NAMES",
123
+ "TRANSCRIPT_PRESETS",
124
+ "CallOutcome",
125
+ "CellKey",
126
+ "CellParams",
127
+ "CellSummary",
128
+ "Contradiction",
129
+ "FactOutcome",
130
+ "FillPlan",
131
+ "IdentifiedHistoryProvider",
132
+ "JointOutcome",
133
+ "JointVerdict",
134
+ "LiveOutcome",
135
+ "MeteredClient",
136
+ "ModelCall",
137
+ "SummarizerUsage",
138
+ "ModelPricing",
139
+ "PlantedFact",
140
+ "ProbeOutcome",
141
+ "ProviderCaller",
142
+ "ProviderRuntime",
143
+ "ProviderSpec",
144
+ "RecallScenario",
145
+ "RecallScore",
146
+ "SeedRecord",
147
+ "StrategyCost",
148
+ "StrategyOptions",
149
+ "StrategySettings",
150
+ "Transcript",
151
+ "TranscriptPreset",
152
+ "TranscriptTurn",
153
+ "TurnCaller",
154
+ "TurnRecord",
155
+ "UsageRecorder",
156
+ "Verdict",
157
+ "WorkloadSettings",
158
+ "MafCachebenchExperimentalWarning",
159
+ "__version__",
160
+ "advise",
161
+ "advise_main",
162
+ "append_seed_record",
163
+ "build_live_agent",
164
+ "build_live_scenario",
165
+ "build_parser",
166
+ "build_preset",
167
+ "build_provider",
168
+ "build_recall_scenario",
169
+ "build_strategy",
170
+ "build_tokenizer",
171
+ "build_transcript",
172
+ "common_message_prefix",
173
+ "cost_of",
174
+ "fetch_openrouter_pricing",
175
+ "find_nested_strategy",
176
+ "forces_records",
177
+ "group_by_cell",
178
+ "live_main",
179
+ "main",
180
+ "make_lookup_tool",
181
+ "parse_provider_selector",
182
+ "percentile",
183
+ "plan_fill",
184
+ "probe_count",
185
+ "prompt_cache_key",
186
+ "prompt_cache_key_options",
187
+ "provider_names",
188
+ "read_seed_records",
189
+ "recall_main",
190
+ "recommend",
191
+ "relative_correctness",
192
+ "render_summary_table",
193
+ "resolve_context_window",
194
+ "restore_state",
195
+ "run_benchmark",
196
+ "run_cell",
197
+ "run_live",
198
+ "score_answer",
199
+ "score_combined_samples",
200
+ "score_samples",
201
+ "serialize_history",
202
+ "serialize_message",
203
+ "snapshot_state",
204
+ "strategy_names",
205
+ "summarize_cell",
206
+ "summary_main",
207
+ "token_counts",
208
+ "unretrieved_facts",
209
+ "unsupported_option",
210
+ "wants_client_side_history",
211
+ "write_records_jsonl",
212
+ "write_summary_csv",
213
+ ]
214
+
215
+ # Experimental package: importing it emits a UserWarning rather than a FutureWarning, so a host
216
+ # running under `python -W error` can still import it.
217
+ import warnings as _warnings
218
+
219
+
220
+ class MafCachebenchExperimentalWarning(UserWarning):
221
+ """Warning category for maf-cachebench's experimental-package notice."""
222
+
223
+
224
+ def _warn_experimental() -> None:
225
+ message = (
226
+ "maf_cachebench is experimental and may change or be removed in future versions "
227
+ "without notice."
228
+ )
229
+ try:
230
+ _warnings.warn(message, category=MafCachebenchExperimentalWarning, stacklevel=2)
231
+ except MafCachebenchExperimentalWarning:
232
+ # Deliberate: under `-W error` an informational notice must not fail the import.
233
+ pass
234
+
235
+
236
+ _warn_experimental()