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.
- maf_cachebench-0.1.0/LICENSE +21 -0
- maf_cachebench-0.1.0/PKG-INFO +125 -0
- maf_cachebench-0.1.0/README.md +86 -0
- maf_cachebench-0.1.0/pyproject.toml +116 -0
- maf_cachebench-0.1.0/pyproject.toml.orig +94 -0
- maf_cachebench-0.1.0/src/maf_cachebench/__init__.py +236 -0
- maf_cachebench-0.1.0/src/maf_cachebench/__main__.py +8 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_advise_cli.py +303 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_advisor.py +461 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_cli.py +456 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_cli_selection.py +194 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_fill.py +454 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_live.py +2319 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_live_cli.py +4371 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_metrics.py +176 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_providers.py +424 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_recall.py +767 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_recall_cli.py +238 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_records.py +1771 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_report.py +162 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_run_identity.py +9 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_runner.py +529 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_strategies.py +745 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_summary.py +335 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_summary_cli.py +341 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_tokenizers.py +200 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_transcripts.py +387 -0
- maf_cachebench-0.1.0/src/maf_cachebench/_types.py +200 -0
- 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
|
+
[](https://pypi.org/project/maf-cachebench/) [](https://pypi.org/project/maf-cachebench/) [](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
|
+
[](https://pypi.org/project/maf-cachebench/) [](https://pypi.org/project/maf-cachebench/) [](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()
|