fast-mode-kit 0.2.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.
- fast_mode_kit-0.2.0/LICENSE +21 -0
- fast_mode_kit-0.2.0/PKG-INFO +106 -0
- fast_mode_kit-0.2.0/README.md +87 -0
- fast_mode_kit-0.2.0/fast_mode_kit.egg-info/PKG-INFO +106 -0
- fast_mode_kit-0.2.0/fast_mode_kit.egg-info/SOURCES.txt +53 -0
- fast_mode_kit-0.2.0/fast_mode_kit.egg-info/dependency_links.txt +1 -0
- fast_mode_kit-0.2.0/fast_mode_kit.egg-info/entry_points.txt +2 -0
- fast_mode_kit-0.2.0/fast_mode_kit.egg-info/requires.txt +6 -0
- fast_mode_kit-0.2.0/fast_mode_kit.egg-info/top_level.txt +1 -0
- fast_mode_kit-0.2.0/fastkit/__init__.py +0 -0
- fast_mode_kit-0.2.0/fastkit/__main__.py +5 -0
- fast_mode_kit-0.2.0/fastkit/checks.py +131 -0
- fast_mode_kit-0.2.0/fastkit/cli.py +190 -0
- fast_mode_kit-0.2.0/fastkit/client.py +77 -0
- fast_mode_kit-0.2.0/fastkit/evals/__init__.py +0 -0
- fast_mode_kit-0.2.0/fastkit/evals/report.py +48 -0
- fast_mode_kit-0.2.0/fastkit/evals/run.py +96 -0
- fast_mode_kit-0.2.0/fastkit/evals/schema.py +50 -0
- fast_mode_kit-0.2.0/fastkit/evals/shop_gold.jsonl +24 -0
- fast_mode_kit-0.2.0/fastkit/examples/__init__.py +0 -0
- fast_mode_kit-0.2.0/fastkit/examples/shop/__init__.py +0 -0
- fast_mode_kit-0.2.0/fastkit/examples/shop/agent.py +104 -0
- fast_mode_kit-0.2.0/fastkit/examples/shop/data.py +32 -0
- fast_mode_kit-0.2.0/fastkit/examples/shop/demo_model.py +69 -0
- fast_mode_kit-0.2.0/fastkit/examples/shop/tools.py +73 -0
- fast_mode_kit-0.2.0/fastkit/hedge.py +84 -0
- fast_mode_kit-0.2.0/fastkit/loop.py +149 -0
- fast_mode_kit-0.2.0/fastkit/mock.py +32 -0
- fast_mode_kit-0.2.0/fastkit/modes.py +20 -0
- fast_mode_kit-0.2.0/fastkit/pipeline.py +56 -0
- fast_mode_kit-0.2.0/fastkit/prompts.py +21 -0
- fast_mode_kit-0.2.0/fastkit/py.typed +0 -0
- fast_mode_kit-0.2.0/fastkit/scaffold/README.md +11 -0
- fast_mode_kit-0.2.0/fastkit/scaffold/agent.py +84 -0
- fast_mode_kit-0.2.0/fastkit/scaffold/gold.jsonl +3 -0
- fast_mode_kit-0.2.0/fastkit/scaffold/tools.py +37 -0
- fast_mode_kit-0.2.0/fastkit/templates/fast_system.md +21 -0
- fast_mode_kit-0.2.0/fastkit/templates/notes.md +9 -0
- fast_mode_kit-0.2.0/fastkit/templates/reader.md +7 -0
- fast_mode_kit-0.2.0/fastkit/templates/writer.md +5 -0
- fast_mode_kit-0.2.0/fastkit/tools.py +65 -0
- fast_mode_kit-0.2.0/fastkit/types.py +47 -0
- fast_mode_kit-0.2.0/pyproject.toml +31 -0
- fast_mode_kit-0.2.0/setup.cfg +4 -0
- fast_mode_kit-0.2.0/tests/test_checks.py +55 -0
- fast_mode_kit-0.2.0/tests/test_cli.py +64 -0
- fast_mode_kit-0.2.0/tests/test_client.py +51 -0
- fast_mode_kit-0.2.0/tests/test_evals.py +41 -0
- fast_mode_kit-0.2.0/tests/test_example_agent.py +39 -0
- fast_mode_kit-0.2.0/tests/test_hedge.py +34 -0
- fast_mode_kit-0.2.0/tests/test_loop.py +138 -0
- fast_mode_kit-0.2.0/tests/test_mock.py +22 -0
- fast_mode_kit-0.2.0/tests/test_modes.py +13 -0
- fast_mode_kit-0.2.0/tests/test_prompts.py +22 -0
- fast_mode_kit-0.2.0/tests/test_tools.py +14 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 fast-mode-kit contributors
|
|
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,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fast-mode-kit
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Starter kit for adding a fast, data-only answer mode to any chat agent
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: llm,agents,latency,tool-calling,evals
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: httpx>=0.27
|
|
14
|
+
Requires-Dist: pydantic>=2.6
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# fast-mode-kit
|
|
21
|
+
|
|
22
|
+
A starter kit for adding a **Fast mode** to any chat agent. Fast mode answers exactly what was asked, from your data, right the first time, in a few seconds.
|
|
23
|
+
|
|
24
|
+
It comes from shipping Fast mode in a live product. Every rule here traces back to a measured result or a real failure, and the [failure catalogue](docs/08-failure-catalogue.md) lists them.
|
|
25
|
+
|
|
26
|
+
It works with **frontier APIs** (any OpenAI-compatible endpoint) and with **models you host yourself** (mlx, vLLM, Ollama, llama.cpp).
|
|
27
|
+
|
|
28
|
+
## Quick start (offline, no key, no cost)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install fast-mode-kit
|
|
32
|
+
|
|
33
|
+
fastkit demo "rank products by revenue" # the example shop agent, offline
|
|
34
|
+
fastkit eval # accuracy, latency, cost and fallback report
|
|
35
|
+
fastkit init my-agent # your own Fast mode project
|
|
36
|
+
cd my-agent && python agent.py "how many widgets in stock?"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The offline stand-in model picks tools by word overlap and has no understanding, so the example scores about 40% on `fastkit eval`. That is expected: it shows what keyword routing gets you. A real model fills the gap.
|
|
40
|
+
|
|
41
|
+
## With a real model
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
export FASTKIT_API_KEY=... # leave unset for local servers (Ollama, vLLM, mlx, llama.cpp)
|
|
45
|
+
|
|
46
|
+
# 1. One tiny request: is this model fast enough, and does it call tools?
|
|
47
|
+
fastkit check --base-url https://openrouter.ai/api/v1 --model google/gemini-2.5-flash
|
|
48
|
+
|
|
49
|
+
# 2. Measure your agent on your questions, with a spend cap
|
|
50
|
+
fastkit eval --agent agent.py --gold gold.jsonl \
|
|
51
|
+
--base-url https://openrouter.ai/api/v1 --model google/gemini-2.5-flash \
|
|
52
|
+
--price-in 0.30 --price-out 2.50 --spend-cap 0.50
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`fastkit check` prints PASS, SLOW or FAIL against the 4.5 s first-words budget and tells you whether the model called the tool. A model that answers without calling it can't run the one-call loop.
|
|
56
|
+
|
|
57
|
+
`fastkit.checks.safe_filter` runs after the answer has finished. Use it in evals and logs, or buffer the output if you want to filter before the user sees it.
|
|
58
|
+
|
|
59
|
+
## Develop
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone <this repo> && cd fast-mode-kit
|
|
63
|
+
pip install -e ".[dev]" && pytest -q # every test runs against a mock model
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## What's inside
|
|
67
|
+
|
|
68
|
+
| Path | What it is |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `docs/` | the guide, chapters 1-9: what Fast is, architecture, deadlines, prompts, checks, model choice, self-hosting, failure catalogue, measuring |
|
|
71
|
+
| `fastkit/templates/` | prompt templates: Fast system, reader, writer, server-owned lines |
|
|
72
|
+
| `fastkit/client.py` | streaming client for any OpenAI-compatible server |
|
|
73
|
+
| `fastkit/loop.py` | the one-call loop: read, call tools and write in one conversation, with a fallback |
|
|
74
|
+
| `fastkit/pipeline.py` | the fallback: a reader plans, the server fetches, a writer answers |
|
|
75
|
+
| `fastkit/hedge.py` | hedged routes; a stream that has started is never cut |
|
|
76
|
+
| `fastkit/tools.py` | tool registry, `find_tools`, `note_turn`, propose/confirm writes |
|
|
77
|
+
| `fastkit/checks.py` | do-no-harm figure checks |
|
|
78
|
+
| `fastkit/modes.py` | Fast-only gate |
|
|
79
|
+
| `fastkit/mock.py` | scripted model for offline tests |
|
|
80
|
+
| `fastkit/examples/shop/` | a complete agent over a toy shop database |
|
|
81
|
+
| `fastkit/evals/` | gold-set runner, report and spend cap |
|
|
82
|
+
| `fastkit/cli.py` | the `fastkit` command: demo, init, check, eval |
|
|
83
|
+
| `fastkit/scaffold/` | the starter project `fastkit init` writes |
|
|
84
|
+
|
|
85
|
+
## Adapt it to your domain in 5 steps
|
|
86
|
+
|
|
87
|
+
1. **Tools:** write read tools that return rows with every derived figure already computed (totals, ranks, percentages), plus propose tools for writes. See `fastkit/examples/shop/tools.py`.
|
|
88
|
+
2. **Prompt:** render `fastkit/templates/fast_system.md` with your domain and tool list.
|
|
89
|
+
3. **Loop:** call `fast_answer(...)` with your model and a pipeline fallback. See `fastkit/examples/shop/agent.py`.
|
|
90
|
+
4. **Gold set:** write 40-60 real questions with their expected figures, across the languages your users write in.
|
|
91
|
+
5. **Measure:** run `fastkit eval` on two or three models and pick by tail latency and per-language accuracy, not by the median.
|
|
92
|
+
|
|
93
|
+
## The rules in one screen
|
|
94
|
+
|
|
95
|
+
- No second chance in Fast, so the first answer must be right.
|
|
96
|
+
- One line per asked part, figure first, data only.
|
|
97
|
+
- The server computes every figure. The model copies.
|
|
98
|
+
- Fall back to another model path, never to keyword guesses.
|
|
99
|
+
- A deadline never cuts an answer that has started streaming.
|
|
100
|
+
- A check that edits a correct line is a bug in the check.
|
|
101
|
+
- Keep Fast changes behind one gate, and prove the other modes unchanged.
|
|
102
|
+
- Never judge a reading model with thinking switched off.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# fast-mode-kit
|
|
2
|
+
|
|
3
|
+
A starter kit for adding a **Fast mode** to any chat agent. Fast mode answers exactly what was asked, from your data, right the first time, in a few seconds.
|
|
4
|
+
|
|
5
|
+
It comes from shipping Fast mode in a live product. Every rule here traces back to a measured result or a real failure, and the [failure catalogue](docs/08-failure-catalogue.md) lists them.
|
|
6
|
+
|
|
7
|
+
It works with **frontier APIs** (any OpenAI-compatible endpoint) and with **models you host yourself** (mlx, vLLM, Ollama, llama.cpp).
|
|
8
|
+
|
|
9
|
+
## Quick start (offline, no key, no cost)
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install fast-mode-kit
|
|
13
|
+
|
|
14
|
+
fastkit demo "rank products by revenue" # the example shop agent, offline
|
|
15
|
+
fastkit eval # accuracy, latency, cost and fallback report
|
|
16
|
+
fastkit init my-agent # your own Fast mode project
|
|
17
|
+
cd my-agent && python agent.py "how many widgets in stock?"
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The offline stand-in model picks tools by word overlap and has no understanding, so the example scores about 40% on `fastkit eval`. That is expected: it shows what keyword routing gets you. A real model fills the gap.
|
|
21
|
+
|
|
22
|
+
## With a real model
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
export FASTKIT_API_KEY=... # leave unset for local servers (Ollama, vLLM, mlx, llama.cpp)
|
|
26
|
+
|
|
27
|
+
# 1. One tiny request: is this model fast enough, and does it call tools?
|
|
28
|
+
fastkit check --base-url https://openrouter.ai/api/v1 --model google/gemini-2.5-flash
|
|
29
|
+
|
|
30
|
+
# 2. Measure your agent on your questions, with a spend cap
|
|
31
|
+
fastkit eval --agent agent.py --gold gold.jsonl \
|
|
32
|
+
--base-url https://openrouter.ai/api/v1 --model google/gemini-2.5-flash \
|
|
33
|
+
--price-in 0.30 --price-out 2.50 --spend-cap 0.50
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
`fastkit check` prints PASS, SLOW or FAIL against the 4.5 s first-words budget and tells you whether the model called the tool. A model that answers without calling it can't run the one-call loop.
|
|
37
|
+
|
|
38
|
+
`fastkit.checks.safe_filter` runs after the answer has finished. Use it in evals and logs, or buffer the output if you want to filter before the user sees it.
|
|
39
|
+
|
|
40
|
+
## Develop
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
git clone <this repo> && cd fast-mode-kit
|
|
44
|
+
pip install -e ".[dev]" && pytest -q # every test runs against a mock model
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## What's inside
|
|
48
|
+
|
|
49
|
+
| Path | What it is |
|
|
50
|
+
|---|---|
|
|
51
|
+
| `docs/` | the guide, chapters 1-9: what Fast is, architecture, deadlines, prompts, checks, model choice, self-hosting, failure catalogue, measuring |
|
|
52
|
+
| `fastkit/templates/` | prompt templates: Fast system, reader, writer, server-owned lines |
|
|
53
|
+
| `fastkit/client.py` | streaming client for any OpenAI-compatible server |
|
|
54
|
+
| `fastkit/loop.py` | the one-call loop: read, call tools and write in one conversation, with a fallback |
|
|
55
|
+
| `fastkit/pipeline.py` | the fallback: a reader plans, the server fetches, a writer answers |
|
|
56
|
+
| `fastkit/hedge.py` | hedged routes; a stream that has started is never cut |
|
|
57
|
+
| `fastkit/tools.py` | tool registry, `find_tools`, `note_turn`, propose/confirm writes |
|
|
58
|
+
| `fastkit/checks.py` | do-no-harm figure checks |
|
|
59
|
+
| `fastkit/modes.py` | Fast-only gate |
|
|
60
|
+
| `fastkit/mock.py` | scripted model for offline tests |
|
|
61
|
+
| `fastkit/examples/shop/` | a complete agent over a toy shop database |
|
|
62
|
+
| `fastkit/evals/` | gold-set runner, report and spend cap |
|
|
63
|
+
| `fastkit/cli.py` | the `fastkit` command: demo, init, check, eval |
|
|
64
|
+
| `fastkit/scaffold/` | the starter project `fastkit init` writes |
|
|
65
|
+
|
|
66
|
+
## Adapt it to your domain in 5 steps
|
|
67
|
+
|
|
68
|
+
1. **Tools:** write read tools that return rows with every derived figure already computed (totals, ranks, percentages), plus propose tools for writes. See `fastkit/examples/shop/tools.py`.
|
|
69
|
+
2. **Prompt:** render `fastkit/templates/fast_system.md` with your domain and tool list.
|
|
70
|
+
3. **Loop:** call `fast_answer(...)` with your model and a pipeline fallback. See `fastkit/examples/shop/agent.py`.
|
|
71
|
+
4. **Gold set:** write 40-60 real questions with their expected figures, across the languages your users write in.
|
|
72
|
+
5. **Measure:** run `fastkit eval` on two or three models and pick by tail latency and per-language accuracy, not by the median.
|
|
73
|
+
|
|
74
|
+
## The rules in one screen
|
|
75
|
+
|
|
76
|
+
- No second chance in Fast, so the first answer must be right.
|
|
77
|
+
- One line per asked part, figure first, data only.
|
|
78
|
+
- The server computes every figure. The model copies.
|
|
79
|
+
- Fall back to another model path, never to keyword guesses.
|
|
80
|
+
- A deadline never cuts an answer that has started streaming.
|
|
81
|
+
- A check that edits a correct line is a bug in the check.
|
|
82
|
+
- Keep Fast changes behind one gate, and prove the other modes unchanged.
|
|
83
|
+
- Never judge a reading model with thinking switched off.
|
|
84
|
+
|
|
85
|
+
## License
|
|
86
|
+
|
|
87
|
+
MIT
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: fast-mode-kit
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Starter kit for adding a fast, data-only answer mode to any chat agent
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: llm,agents,latency,tool-calling,evals
|
|
7
|
+
Classifier: Programming Language :: Python :: 3
|
|
8
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
9
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
10
|
+
Requires-Python: >=3.11
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: httpx>=0.27
|
|
14
|
+
Requires-Dist: pydantic>=2.6
|
|
15
|
+
Provides-Extra: dev
|
|
16
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
17
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
18
|
+
Dynamic: license-file
|
|
19
|
+
|
|
20
|
+
# fast-mode-kit
|
|
21
|
+
|
|
22
|
+
A starter kit for adding a **Fast mode** to any chat agent. Fast mode answers exactly what was asked, from your data, right the first time, in a few seconds.
|
|
23
|
+
|
|
24
|
+
It comes from shipping Fast mode in a live product. Every rule here traces back to a measured result or a real failure, and the [failure catalogue](docs/08-failure-catalogue.md) lists them.
|
|
25
|
+
|
|
26
|
+
It works with **frontier APIs** (any OpenAI-compatible endpoint) and with **models you host yourself** (mlx, vLLM, Ollama, llama.cpp).
|
|
27
|
+
|
|
28
|
+
## Quick start (offline, no key, no cost)
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install fast-mode-kit
|
|
32
|
+
|
|
33
|
+
fastkit demo "rank products by revenue" # the example shop agent, offline
|
|
34
|
+
fastkit eval # accuracy, latency, cost and fallback report
|
|
35
|
+
fastkit init my-agent # your own Fast mode project
|
|
36
|
+
cd my-agent && python agent.py "how many widgets in stock?"
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The offline stand-in model picks tools by word overlap and has no understanding, so the example scores about 40% on `fastkit eval`. That is expected: it shows what keyword routing gets you. A real model fills the gap.
|
|
40
|
+
|
|
41
|
+
## With a real model
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
export FASTKIT_API_KEY=... # leave unset for local servers (Ollama, vLLM, mlx, llama.cpp)
|
|
45
|
+
|
|
46
|
+
# 1. One tiny request: is this model fast enough, and does it call tools?
|
|
47
|
+
fastkit check --base-url https://openrouter.ai/api/v1 --model google/gemini-2.5-flash
|
|
48
|
+
|
|
49
|
+
# 2. Measure your agent on your questions, with a spend cap
|
|
50
|
+
fastkit eval --agent agent.py --gold gold.jsonl \
|
|
51
|
+
--base-url https://openrouter.ai/api/v1 --model google/gemini-2.5-flash \
|
|
52
|
+
--price-in 0.30 --price-out 2.50 --spend-cap 0.50
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`fastkit check` prints PASS, SLOW or FAIL against the 4.5 s first-words budget and tells you whether the model called the tool. A model that answers without calling it can't run the one-call loop.
|
|
56
|
+
|
|
57
|
+
`fastkit.checks.safe_filter` runs after the answer has finished. Use it in evals and logs, or buffer the output if you want to filter before the user sees it.
|
|
58
|
+
|
|
59
|
+
## Develop
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
git clone <this repo> && cd fast-mode-kit
|
|
63
|
+
pip install -e ".[dev]" && pytest -q # every test runs against a mock model
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## What's inside
|
|
67
|
+
|
|
68
|
+
| Path | What it is |
|
|
69
|
+
|---|---|
|
|
70
|
+
| `docs/` | the guide, chapters 1-9: what Fast is, architecture, deadlines, prompts, checks, model choice, self-hosting, failure catalogue, measuring |
|
|
71
|
+
| `fastkit/templates/` | prompt templates: Fast system, reader, writer, server-owned lines |
|
|
72
|
+
| `fastkit/client.py` | streaming client for any OpenAI-compatible server |
|
|
73
|
+
| `fastkit/loop.py` | the one-call loop: read, call tools and write in one conversation, with a fallback |
|
|
74
|
+
| `fastkit/pipeline.py` | the fallback: a reader plans, the server fetches, a writer answers |
|
|
75
|
+
| `fastkit/hedge.py` | hedged routes; a stream that has started is never cut |
|
|
76
|
+
| `fastkit/tools.py` | tool registry, `find_tools`, `note_turn`, propose/confirm writes |
|
|
77
|
+
| `fastkit/checks.py` | do-no-harm figure checks |
|
|
78
|
+
| `fastkit/modes.py` | Fast-only gate |
|
|
79
|
+
| `fastkit/mock.py` | scripted model for offline tests |
|
|
80
|
+
| `fastkit/examples/shop/` | a complete agent over a toy shop database |
|
|
81
|
+
| `fastkit/evals/` | gold-set runner, report and spend cap |
|
|
82
|
+
| `fastkit/cli.py` | the `fastkit` command: demo, init, check, eval |
|
|
83
|
+
| `fastkit/scaffold/` | the starter project `fastkit init` writes |
|
|
84
|
+
|
|
85
|
+
## Adapt it to your domain in 5 steps
|
|
86
|
+
|
|
87
|
+
1. **Tools:** write read tools that return rows with every derived figure already computed (totals, ranks, percentages), plus propose tools for writes. See `fastkit/examples/shop/tools.py`.
|
|
88
|
+
2. **Prompt:** render `fastkit/templates/fast_system.md` with your domain and tool list.
|
|
89
|
+
3. **Loop:** call `fast_answer(...)` with your model and a pipeline fallback. See `fastkit/examples/shop/agent.py`.
|
|
90
|
+
4. **Gold set:** write 40-60 real questions with their expected figures, across the languages your users write in.
|
|
91
|
+
5. **Measure:** run `fastkit eval` on two or three models and pick by tail latency and per-language accuracy, not by the median.
|
|
92
|
+
|
|
93
|
+
## The rules in one screen
|
|
94
|
+
|
|
95
|
+
- No second chance in Fast, so the first answer must be right.
|
|
96
|
+
- One line per asked part, figure first, data only.
|
|
97
|
+
- The server computes every figure. The model copies.
|
|
98
|
+
- Fall back to another model path, never to keyword guesses.
|
|
99
|
+
- A deadline never cuts an answer that has started streaming.
|
|
100
|
+
- A check that edits a correct line is a bug in the check.
|
|
101
|
+
- Keep Fast changes behind one gate, and prove the other modes unchanged.
|
|
102
|
+
- Never judge a reading model with thinking switched off.
|
|
103
|
+
|
|
104
|
+
## License
|
|
105
|
+
|
|
106
|
+
MIT
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
fast_mode_kit.egg-info/PKG-INFO
|
|
5
|
+
fast_mode_kit.egg-info/SOURCES.txt
|
|
6
|
+
fast_mode_kit.egg-info/dependency_links.txt
|
|
7
|
+
fast_mode_kit.egg-info/entry_points.txt
|
|
8
|
+
fast_mode_kit.egg-info/requires.txt
|
|
9
|
+
fast_mode_kit.egg-info/top_level.txt
|
|
10
|
+
fastkit/__init__.py
|
|
11
|
+
fastkit/__main__.py
|
|
12
|
+
fastkit/checks.py
|
|
13
|
+
fastkit/cli.py
|
|
14
|
+
fastkit/client.py
|
|
15
|
+
fastkit/hedge.py
|
|
16
|
+
fastkit/loop.py
|
|
17
|
+
fastkit/mock.py
|
|
18
|
+
fastkit/modes.py
|
|
19
|
+
fastkit/pipeline.py
|
|
20
|
+
fastkit/prompts.py
|
|
21
|
+
fastkit/py.typed
|
|
22
|
+
fastkit/tools.py
|
|
23
|
+
fastkit/types.py
|
|
24
|
+
fastkit/evals/__init__.py
|
|
25
|
+
fastkit/evals/report.py
|
|
26
|
+
fastkit/evals/run.py
|
|
27
|
+
fastkit/evals/schema.py
|
|
28
|
+
fastkit/evals/shop_gold.jsonl
|
|
29
|
+
fastkit/examples/__init__.py
|
|
30
|
+
fastkit/examples/shop/__init__.py
|
|
31
|
+
fastkit/examples/shop/agent.py
|
|
32
|
+
fastkit/examples/shop/data.py
|
|
33
|
+
fastkit/examples/shop/demo_model.py
|
|
34
|
+
fastkit/examples/shop/tools.py
|
|
35
|
+
fastkit/scaffold/README.md
|
|
36
|
+
fastkit/scaffold/agent.py
|
|
37
|
+
fastkit/scaffold/gold.jsonl
|
|
38
|
+
fastkit/scaffold/tools.py
|
|
39
|
+
fastkit/templates/fast_system.md
|
|
40
|
+
fastkit/templates/notes.md
|
|
41
|
+
fastkit/templates/reader.md
|
|
42
|
+
fastkit/templates/writer.md
|
|
43
|
+
tests/test_checks.py
|
|
44
|
+
tests/test_cli.py
|
|
45
|
+
tests/test_client.py
|
|
46
|
+
tests/test_evals.py
|
|
47
|
+
tests/test_example_agent.py
|
|
48
|
+
tests/test_hedge.py
|
|
49
|
+
tests/test_loop.py
|
|
50
|
+
tests/test_mock.py
|
|
51
|
+
tests/test_modes.py
|
|
52
|
+
tests/test_prompts.py
|
|
53
|
+
tests/test_tools.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
fastkit
|
|
File without changes
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
"""Do-no-harm figure checks.
|
|
2
|
+
|
|
3
|
+
Rules learned the hard way:
|
|
4
|
+
- an identifier that appears in the rows is sourced, even if it has digits;
|
|
5
|
+
- a zero or negative that answers the question is said, never trimmed;
|
|
6
|
+
- a sentence is dropped only when it has an unsourced figure AND no sourced
|
|
7
|
+
one; a check never leaves an empty answer or a fragment.
|
|
8
|
+
Every edit a check makes to a correct line is a bug in the check.
|
|
9
|
+
"""
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import logging
|
|
13
|
+
import re
|
|
14
|
+
from typing import Any, Iterable
|
|
15
|
+
|
|
16
|
+
log = logging.getLogger(__name__)
|
|
17
|
+
|
|
18
|
+
_NUM = re.compile(r"(?<![\w.])-?\d[\d,]*(?:\.\d+)?%?")
|
|
19
|
+
_RANGE_DASH = re.compile(r"(?<=\d)-(?=\d)")
|
|
20
|
+
_LIST_MARK = re.compile(r"^\s*\d+[.)]\s+")
|
|
21
|
+
_TOKEN = re.compile(r"[A-Za-z0-9][A-Za-z0-9_\-./]*[A-Za-z0-9]|[A-Za-z0-9]")
|
|
22
|
+
_SENT = re.compile(r"(?<=[.!?])\s+")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _walk(obj: Any) -> Iterable[Any]:
|
|
26
|
+
if isinstance(obj, dict):
|
|
27
|
+
for v in obj.values():
|
|
28
|
+
yield from _walk(v)
|
|
29
|
+
elif isinstance(obj, (list, tuple)):
|
|
30
|
+
for v in obj:
|
|
31
|
+
yield from _walk(v)
|
|
32
|
+
else:
|
|
33
|
+
yield obj
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def _row_facts(rows: list[dict], question: str) -> tuple[list[float], set[str]]:
|
|
37
|
+
nums: list[float] = []
|
|
38
|
+
strings: set[str] = set()
|
|
39
|
+
for v in _walk(rows):
|
|
40
|
+
if isinstance(v, bool):
|
|
41
|
+
continue
|
|
42
|
+
if isinstance(v, (int, float)):
|
|
43
|
+
nums.append(float(v))
|
|
44
|
+
elif isinstance(v, str):
|
|
45
|
+
strings.add(v.lower())
|
|
46
|
+
try:
|
|
47
|
+
nums.append(float(v.replace(",", "")))
|
|
48
|
+
except ValueError:
|
|
49
|
+
pass
|
|
50
|
+
for m in _NUM.finditer(question):
|
|
51
|
+
nums.append(float(m.group(0).rstrip("%").replace(",", "")))
|
|
52
|
+
strings.add(question.lower())
|
|
53
|
+
return nums, strings
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _matches(text: str, nums: list[float]) -> bool:
|
|
57
|
+
pct = text.endswith("%")
|
|
58
|
+
raw = text.rstrip("%").replace(",", "")
|
|
59
|
+
try:
|
|
60
|
+
val = float(raw)
|
|
61
|
+
except ValueError:
|
|
62
|
+
return False
|
|
63
|
+
decimals = len(raw.split(".")[1]) if "." in raw else 0
|
|
64
|
+
for n in nums:
|
|
65
|
+
for cand in ((n, n * 100) if pct else (n,)):
|
|
66
|
+
for c in (cand, abs(cand)) if val >= 0 else (cand,):
|
|
67
|
+
# "fell 3.2%" states -3.2 without its sign
|
|
68
|
+
if round(c, decimals) == val or c == val:
|
|
69
|
+
return True
|
|
70
|
+
if decimals == 0 and abs(c) >= 1 and round(c) == val:
|
|
71
|
+
return True
|
|
72
|
+
return False
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _figures(answer: str) -> list[tuple[str, bool]]:
|
|
76
|
+
"""(figure, is_identifier) pairs found in the answer."""
|
|
77
|
+
out = []
|
|
78
|
+
spans = []
|
|
79
|
+
for tok in _TOKEN.finditer(answer):
|
|
80
|
+
t = tok.group(0)
|
|
81
|
+
if not any(c.isdigit() for c in t):
|
|
82
|
+
continue
|
|
83
|
+
# codes (SKU-1042), dates (2026-11-01), versions: identifiers, not quantities
|
|
84
|
+
if re.fullmatch(r"\d+(?:\.\d+)?-\d+(?:\.\d+)?", t):
|
|
85
|
+
continue # a range like 10-20: two plain numbers, handled below
|
|
86
|
+
if any(c.isalpha() for c in t) or "-" in t.lstrip("-") or t.count(".") > 1 or "/" in t:
|
|
87
|
+
out.append((t, True))
|
|
88
|
+
spans.append(tok.span())
|
|
89
|
+
for m in _NUM.finditer(_RANGE_DASH.sub(" ", answer)):
|
|
90
|
+
if any(a <= m.start() < b for a, b in spans):
|
|
91
|
+
continue
|
|
92
|
+
out.append((m.group(0).rstrip(".,"), False))
|
|
93
|
+
return out
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def ungrounded_figures(answer: str, rows: list[dict], question: str = "") -> list[str]:
|
|
97
|
+
nums, strings = _row_facts(rows, question)
|
|
98
|
+
bad = []
|
|
99
|
+
for fig, ident in _figures(answer):
|
|
100
|
+
if ident:
|
|
101
|
+
if not any(fig.lower() in s for s in strings):
|
|
102
|
+
bad.append(fig)
|
|
103
|
+
elif not _matches(fig, nums):
|
|
104
|
+
bad.append(fig)
|
|
105
|
+
return bad
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def safe_filter(answer: str, rows: list[dict], question: str = "") -> str:
|
|
109
|
+
"""Drop a sentence only when every figure in it is unsourced. Lines and
|
|
110
|
+
list markers are kept as written; never returns an empty answer."""
|
|
111
|
+
out_lines = []
|
|
112
|
+
dropped = False
|
|
113
|
+
for line in answer.split("\n"):
|
|
114
|
+
mark = _LIST_MARK.match(line)
|
|
115
|
+
prefix, body = (line[:mark.end()], line[mark.end():]) if mark else ("", line)
|
|
116
|
+
kept = []
|
|
117
|
+
for sent in _SENT.split(body):
|
|
118
|
+
figs = [f for f, _ in _figures(sent)]
|
|
119
|
+
bad = ungrounded_figures(sent, rows, question)
|
|
120
|
+
if bad and len(bad) == len(figs):
|
|
121
|
+
log.info("dropped unsourced sentence: %r", sent)
|
|
122
|
+
dropped = True
|
|
123
|
+
continue
|
|
124
|
+
kept.append(sent)
|
|
125
|
+
if kept or not body.strip():
|
|
126
|
+
out_lines.append(prefix + " ".join(kept))
|
|
127
|
+
result = "\n".join(out_lines).strip()
|
|
128
|
+
if not result:
|
|
129
|
+
log.warning("check would empty the answer; keeping original")
|
|
130
|
+
return answer
|
|
131
|
+
return result if dropped else answer
|