small-model-harness 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.
- small_model_harness-0.1.0/LICENSE +21 -0
- small_model_harness-0.1.0/PKG-INFO +426 -0
- small_model_harness-0.1.0/README.md +391 -0
- small_model_harness-0.1.0/pyproject.toml +37 -0
- small_model_harness-0.1.0/setup.cfg +4 -0
- small_model_harness-0.1.0/small_model_harness/__init__.py +874 -0
- small_model_harness-0.1.0/small_model_harness/confidence.py +241 -0
- small_model_harness-0.1.0/small_model_harness/pydantic_deep_integration.py +243 -0
- small_model_harness-0.1.0/small_model_harness/tool_disclosure.py +231 -0
- small_model_harness-0.1.0/small_model_harness/tool_repair.py +432 -0
- small_model_harness-0.1.0/small_model_harness.egg-info/PKG-INFO +426 -0
- small_model_harness-0.1.0/small_model_harness.egg-info/SOURCES.txt +20 -0
- small_model_harness-0.1.0/small_model_harness.egg-info/dependency_links.txt +1 -0
- small_model_harness-0.1.0/small_model_harness.egg-info/requires.txt +21 -0
- small_model_harness-0.1.0/small_model_harness.egg-info/top_level.txt +1 -0
- small_model_harness-0.1.0/tests/test_confidence.py +187 -0
- small_model_harness-0.1.0/tests/test_e2e_integration.py +279 -0
- small_model_harness-0.1.0/tests/test_harness.py +465 -0
- small_model_harness-0.1.0/tests/test_integration.py +124 -0
- small_model_harness-0.1.0/tests/test_thinking.py +112 -0
- small_model_harness-0.1.0/tests/test_tool_disclosure.py +148 -0
- small_model_harness-0.1.0/tests/test_tool_repair.py +297 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 sinply-ehis
|
|
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,426 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: small-model-harness
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Session-level intelligence for small LLMs — failure tracking, adaptive steering, loop detection, response compaction.
|
|
5
|
+
Author: sinply-ehis
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: llm,harness,small-model,tool-calling,agent
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: pydantic>=2.0
|
|
19
|
+
Provides-Extra: pydantic-deep
|
|
20
|
+
Requires-Dist: pydantic-deep>=0.3; extra == "pydantic-deep"
|
|
21
|
+
Requires-Dist: pydantic-ai[duckduckgo]>=0.0.50; extra == "pydantic-deep"
|
|
22
|
+
Provides-Extra: pdf
|
|
23
|
+
Requires-Dist: pymupdf>=1.23; extra == "pdf"
|
|
24
|
+
Provides-Extra: epub
|
|
25
|
+
Requires-Dist: ebooklib>=0.18; extra == "epub"
|
|
26
|
+
Requires-Dist: beautifulsoup4>=4.12; extra == "epub"
|
|
27
|
+
Provides-Extra: full
|
|
28
|
+
Requires-Dist: pymupdf>=1.23; extra == "full"
|
|
29
|
+
Requires-Dist: ebooklib>=0.18; extra == "full"
|
|
30
|
+
Requires-Dist: beautifulsoup4>=4.12; extra == "full"
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
33
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
34
|
+
Dynamic: license-file
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<img src="logo.svg" width="120" alt="small-model-harness logo">
|
|
38
|
+
</p>
|
|
39
|
+
|
|
40
|
+
<h1 align="center">small-model-harness</h1>
|
|
41
|
+
|
|
42
|
+
<p align="center">
|
|
43
|
+
<strong>Session-level intelligence for small LLMs (2B–4B parameters)</strong>
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
<p align="center">
|
|
47
|
+
<a href="#install">Install</a> ·
|
|
48
|
+
<a href="#quick-start">Quick Start</a> ·
|
|
49
|
+
<a href="#features">Features</a> ·
|
|
50
|
+
<a href="#api">API</a> ·
|
|
51
|
+
<a href="#architecture">Architecture</a> ·
|
|
52
|
+
<a href="#research">Research</a>
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
<p align="center">
|
|
58
|
+
<img src="https://img.shields.io/badge/python-3.10+-blue.svg" alt="Python 3.10+">
|
|
59
|
+
<img src="https://img.shields.io/badge/pydantic-v2-ff6b6b.svg" alt="Pydantic v2">
|
|
60
|
+
<img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License MIT">
|
|
61
|
+
<img src="https://img.shields.io/badge/142-tests-passing-brightgreen.svg" alt="142 Tests">
|
|
62
|
+
<img src="https://img.shields.io/badge/size-<10KB-lightgrey.svg" alt="Size">
|
|
63
|
+
</p>
|
|
64
|
+
|
|
65
|
+
<p align="center">
|
|
66
|
+
Small models fail on tool calling in predictable ways. This harness<em> compensates</em> for those failures
|
|
67
|
+
deterministically — no extra LLM calls, no retries, no cloud dependency.
|
|
68
|
+
</p>
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Why this exists
|
|
73
|
+
|
|
74
|
+
A 2B–4B parameter model will:
|
|
75
|
+
|
|
76
|
+
1. **Skip tool calls entirely** — acknowledge uncertainty, then confabulate an answer instead of calling the tool
|
|
77
|
+
2. **Produce malformed JSON** — trailing commas, wrong types, camelCase instead of snake_case
|
|
78
|
+
3. **Select the wrong tool** — when given 20+ options, small models pick randomly
|
|
79
|
+
4. **Get stuck in loops** — call the same tool repeatedly hoping for a different result
|
|
80
|
+
5. **Run out of context** — verbose tool responses fill the window before the task completes
|
|
81
|
+
|
|
82
|
+
This harness fixes all five — deterministically, at the session level, without modifying the model.
|
|
83
|
+
|
|
84
|
+
## Install
|
|
85
|
+
|
|
86
|
+
```bash
|
|
87
|
+
pip install small-model-harness
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
With optional extras:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
pip install "small-model-harness[full]" # PDF + EPUB support
|
|
94
|
+
pip install "small-model-harness[pydantic-deep]" # Full agent framework
|
|
95
|
+
pip install "small-model-harness[dev]" # Testing tools
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Quick start
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from small_model_harness import (
|
|
102
|
+
create_harness_session,
|
|
103
|
+
ExecutionRecord,
|
|
104
|
+
repair_tool_call,
|
|
105
|
+
score_tool_call_confidence,
|
|
106
|
+
rank_tools,
|
|
107
|
+
compact_tool_response,
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
# 1. Create a session
|
|
111
|
+
session = create_harness_session(n_ctx=4096)
|
|
112
|
+
|
|
113
|
+
# 2. Before showing tools, rank by relevance (show top 5, not all 211)
|
|
114
|
+
ranked = rank_tools("play some music", tool_schemas, top_n=5)
|
|
115
|
+
prompt = build_compact_tool_prompt("play some music", tool_schemas, top_n=5)
|
|
116
|
+
|
|
117
|
+
# 3. Model outputs malformed JSON — repair it deterministically
|
|
118
|
+
args, name, fixes = repair_tool_call(
|
|
119
|
+
'{"tool": "play_audio", "arguments": {"filePath": "/a/b.wav", "volume": "0.8"}}',
|
|
120
|
+
tool_schemas,
|
|
121
|
+
)
|
|
122
|
+
# name = "play_audio"
|
|
123
|
+
# args = {"file_path": "/a/b.wav", "volume": 0.8} # renamed + coerced
|
|
124
|
+
# fixes = ["Renamed 'filePath' → 'file_path'", "Converted 'volume' from string to number"]
|
|
125
|
+
|
|
126
|
+
# 4. Score confidence — should we escalate to cloud?
|
|
127
|
+
score = score_tool_call_confidence(name, args, tool_schemas[name], harness=session)
|
|
128
|
+
if score.should_escalate:
|
|
129
|
+
escalate_to_cloud()
|
|
130
|
+
|
|
131
|
+
# 5. Record the execution
|
|
132
|
+
session.record_execution(ExecutionRecord(
|
|
133
|
+
timestamp="2026-08-31T12:00:00Z",
|
|
134
|
+
tool_name=name,
|
|
135
|
+
arguments=args,
|
|
136
|
+
status="completed",
|
|
137
|
+
result="Playing audio...",
|
|
138
|
+
duration_ms=340.0,
|
|
139
|
+
))
|
|
140
|
+
|
|
141
|
+
# 6. Get steering hints for the next system prompt
|
|
142
|
+
steering = session.get_steering_prompt()
|
|
143
|
+
# → "Avoid: play_audio (failed 3 times this session)"
|
|
144
|
+
|
|
145
|
+
# 7. Compact verbose responses to save context
|
|
146
|
+
compacted, was_compacted = compact_tool_response("list_voices", huge_json, max_tokens=200)
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## Features
|
|
150
|
+
|
|
151
|
+
### Deterministic tool repair
|
|
152
|
+
|
|
153
|
+
Fixes malformed output without re-prompting. The model's *intent* is usually correct — only the serialization drifted.
|
|
154
|
+
|
|
155
|
+
| What breaks | How it's fixed |
|
|
156
|
+
|---|---|
|
|
157
|
+
| Trailing commas: `{"a": 1,}` | JSON repair strips them |
|
|
158
|
+
| Wrong types: `"5"` instead of `5` | Type coercion from schema |
|
|
159
|
+
| Key naming: `maxResults` vs `max_results` | Key renaming (camelCase/snake_case) |
|
|
160
|
+
| Missing defaults: model forgets optional args | Default injection from schema |
|
|
161
|
+
| Markdown fences: ` ```json ... ``` ` | Stripped before parsing |
|
|
162
|
+
|
|
163
|
+
```python
|
|
164
|
+
from small_model_harness import repair_tool_call
|
|
165
|
+
|
|
166
|
+
args, name, fixes = repair_tool_call(malformed_output, tool_schemas)
|
|
167
|
+
# Always returns (args, tool_name, fixes_applied) or (None, None, error)
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Confidence scoring
|
|
171
|
+
|
|
172
|
+
Multi-signal scoring to know when to trust the model vs escalate to cloud.
|
|
173
|
+
|
|
174
|
+
| Signal | Weight | What it measures |
|
|
175
|
+
|---|---|---|
|
|
176
|
+
| Schema match | 35% | Are args valid against the tool schema? |
|
|
177
|
+
| History | 25% | Has this tool succeeded before in this session? |
|
|
178
|
+
| Completeness | 25% | Are required arguments present? |
|
|
179
|
+
| Repair cost | 15% | How much did we have to fix? |
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from small_model_harness import score_tool_call_confidence
|
|
183
|
+
|
|
184
|
+
score = score_tool_call_confidence(
|
|
185
|
+
"search_web", args, schema, harness=session, repairs_applied=fixes
|
|
186
|
+
)
|
|
187
|
+
print(score.overall) # 0.85
|
|
188
|
+
print(score.should_escalate) # False
|
|
189
|
+
print(format_confidence_summary(score))
|
|
190
|
+
# Confidence: 85% (HIGH)
|
|
191
|
+
# Schema: 100%
|
|
192
|
+
# History: 90%
|
|
193
|
+
# Completeness: 100%
|
|
194
|
+
# Repair cost: 90%
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Progressive tool disclosure
|
|
198
|
+
|
|
199
|
+
Don't show the model all 211 tools — show the 5 most relevant ones. Reduces selection errors dramatically.
|
|
200
|
+
|
|
201
|
+
```python
|
|
202
|
+
from small_model_harness import rank_tools, build_compact_tool_prompt
|
|
203
|
+
|
|
204
|
+
# Rank tools by query relevance
|
|
205
|
+
ranked = rank_tools("play some music", tool_schemas, top_n=5)
|
|
206
|
+
# Returns: [ToolScore(name="play_audio", score=0.85, ...), ...]
|
|
207
|
+
|
|
208
|
+
# Build a compact prompt with only relevant tools
|
|
209
|
+
prompt = build_compact_tool_prompt("play some music", tool_schemas, top_n=5)
|
|
210
|
+
# "You have access to the following tools:
|
|
211
|
+
# - play_audio: Play an audio file | params: file_path: string*, volume: number
|
|
212
|
+
# - speak_text: Convert text to speech | params: text: string*, voice: string
|
|
213
|
+
# ..."
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
### Adaptive steering
|
|
217
|
+
|
|
218
|
+
After repeated failures, inject "avoid this" hints into the system prompt.
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
session.record_execution(ExecutionRecord(
|
|
222
|
+
timestamp="...", tool_name="play_audio", status="failed", ...
|
|
223
|
+
))
|
|
224
|
+
session.record_execution(ExecutionRecord(
|
|
225
|
+
timestamp="...", tool_name="play_audio", status="failed", ...
|
|
226
|
+
))
|
|
227
|
+
|
|
228
|
+
steering = session.get_steering_prompt()
|
|
229
|
+
# → "Avoid: play_audio (failed 2 times this session)"
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Loop detection
|
|
233
|
+
|
|
234
|
+
Catch when the model calls the same tool repeatedly.
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from small_model_harness import detect_failure_loop
|
|
238
|
+
|
|
239
|
+
calls = ["search", "search", "search"]
|
|
240
|
+
tool = detect_failure_loop(calls)
|
|
241
|
+
# → "search" (loop detected)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Context pressure monitoring
|
|
245
|
+
|
|
246
|
+
Track token usage and trigger compaction before overflow.
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
session.update_context_pressure(estimated_tokens=3500, n_ctx=4096)
|
|
250
|
+
print(session.context_pressure) # 0.85
|
|
251
|
+
print(session.is_context_warned) # True (≥75%)
|
|
252
|
+
print(session.is_context_critical) # False (<90%)
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Response compaction
|
|
256
|
+
|
|
257
|
+
Shrink verbose tool responses to save context space.
|
|
258
|
+
|
|
259
|
+
```python
|
|
260
|
+
from small_model_harness import compact_tool_response
|
|
261
|
+
|
|
262
|
+
compacted, was_compacted = compact_tool_response(
|
|
263
|
+
"play_audio",
|
|
264
|
+
'{"audio_file": "/long/path/file.wav", "duration": 300, "sample_rate": 44100, ...}',
|
|
265
|
+
max_tokens=50,
|
|
266
|
+
)
|
|
267
|
+
# → '{"audio_file": "/long/path/file.wav", "duration": 300}' (compacted)
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
## API
|
|
271
|
+
|
|
272
|
+
### Core functions
|
|
273
|
+
|
|
274
|
+
| Function | Purpose |
|
|
275
|
+
|---|---|
|
|
276
|
+
| `create_harness_session(session_id, budget, n_ctx)` | Create a new session |
|
|
277
|
+
| `repair_tool_call(raw_output, tool_schemas)` | Fix malformed model output |
|
|
278
|
+
| `score_tool_call_confidence(name, args, schema, harness, repairs)` | Score tool call confidence |
|
|
279
|
+
| `rank_tools(query, tool_schemas, harness, top_n)` | Rank tools by relevance |
|
|
280
|
+
| `build_compact_tool_prompt(query, tool_schemas, harness, top_n)` | Build compact tool prompt |
|
|
281
|
+
| `compact_tool_response(tool_name, response, max_tokens)` | Shrink verbose responses |
|
|
282
|
+
| `detect_failure_loop(recent_calls, window)` | Detect same-tool loops |
|
|
283
|
+
| `estimate_tokens(text)` | Rough token estimate |
|
|
284
|
+
| `tool_schema(name, description, properties)` | Generate OpenAI tool schema |
|
|
285
|
+
| `pydantic_tool_schema(model, name, description)` | Generate schema from pydantic model |
|
|
286
|
+
|
|
287
|
+
### HarnessState
|
|
288
|
+
|
|
289
|
+
| Method | Purpose |
|
|
290
|
+
|---|---|
|
|
291
|
+
| `record_execution(record)` | Record a tool call, update stats |
|
|
292
|
+
| `update_context_pressure(tokens, n_ctx)` | Update pressure estimate |
|
|
293
|
+
| `get_steering_prompt()` | Get hints for system prompt |
|
|
294
|
+
| `get_avoided_tools_prompt()` | List tools to avoid |
|
|
295
|
+
| `format_audit_trail()` | Human-readable execution log |
|
|
296
|
+
| `generate_session_summary()` | One-line session summary |
|
|
297
|
+
|
|
298
|
+
| Property | Type | Meaning |
|
|
299
|
+
|---|---|---|
|
|
300
|
+
| `success_rate` | float | 0.0–1.0 |
|
|
301
|
+
| `is_context_warned` | bool | pressure ≥ 0.75 |
|
|
302
|
+
| `is_context_critical` | bool | pressure ≥ 0.90 |
|
|
303
|
+
| `is_budget_exhausted` | bool | no calls remaining |
|
|
304
|
+
| `steering_hints` | list[str] | Current steering hints |
|
|
305
|
+
| `avoided_tools` | set[str] | Tools marked as avoided |
|
|
306
|
+
|
|
307
|
+
### Models (pydantic v2)
|
|
308
|
+
|
|
309
|
+
| Model | Fields |
|
|
310
|
+
|---|---|
|
|
311
|
+
| `HarnessState` | session_id, started_at, budget, execution_history, tool stats, steering, pressure |
|
|
312
|
+
| `ExecutionRecord` | timestamp, tool_name, arguments, status, result, error, duration_ms, confidence |
|
|
313
|
+
| `FileInput` | path, content, mime_type, size_bytes |
|
|
314
|
+
| `ConfidenceScore` | overall, schema_match, history_score, completeness, repair_cost, should_escalate |
|
|
315
|
+
| `ToolScore` | name, score, reason, schema |
|
|
316
|
+
|
|
317
|
+
## Architecture
|
|
318
|
+
|
|
319
|
+
```
|
|
320
|
+
┌──────────────────────────────────────────────────────────────┐
|
|
321
|
+
│ Your application │
|
|
322
|
+
│ (streaming loop, agent, CLI, whatever) │
|
|
323
|
+
├──────────────────────────────────────────────────────────────┤
|
|
324
|
+
│ small-model-harness │
|
|
325
|
+
│ │
|
|
326
|
+
│ ┌─────────────┐ ┌──────────────┐ ┌────────────────────┐ │
|
|
327
|
+
│ │ Tool Repair │ │ Confidence │ │ Tool Disclosure │ │
|
|
328
|
+
│ │ JSON fix │ │ Schema │ │ Rank by relevance │ │
|
|
329
|
+
│ │ Type coerce │ │ History │ │ Top-N selection │ │
|
|
330
|
+
│ │ Key rename │ │ Completeness│ │ Compact prompts │ │
|
|
331
|
+
│ │ Defaults │ │ Repair cost │ │ │ │
|
|
332
|
+
│ └─────────────┘ └──────────────┘ └────────────────────┘ │
|
|
333
|
+
│ │
|
|
334
|
+
│ ┌──────────────────────────────────────────────────────┐ │
|
|
335
|
+
│ │ Session Intelligence │ │
|
|
336
|
+
│ │ Failure tracking · Adaptive steering · Loop detection │ │
|
|
337
|
+
│ │ Context pressure · Response compaction · Audit trail │ │
|
|
338
|
+
│ └──────────────────────────────────────────────────────┘ │
|
|
339
|
+
├──────────────────────────────────────────────────────────────┤
|
|
340
|
+
│ llama.cpp / vLLM / OpenAI-compatible endpoint │
|
|
341
|
+
└──────────────────────────────────────────────────────────────┘
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
### How the pieces fit together
|
|
345
|
+
|
|
346
|
+
```python
|
|
347
|
+
# The full pipeline — from raw model output to repaired, scored, tracked call
|
|
348
|
+
|
|
349
|
+
# 1. Model outputs something broken
|
|
350
|
+
raw = model.generate(prompt)
|
|
351
|
+
|
|
352
|
+
# 2. Repair it
|
|
353
|
+
args, name, fixes = repair_tool_call(raw, tool_schemas)
|
|
354
|
+
|
|
355
|
+
# 3. Score confidence
|
|
356
|
+
score = score_tool_call_confidence(name, args, tool_schemas[name], session, fixes)
|
|
357
|
+
|
|
358
|
+
# 4. If confidence is low, escalate
|
|
359
|
+
if score.should_escalate:
|
|
360
|
+
raw = cloud_model.generate(prompt)
|
|
361
|
+
args, name, fixes = repair_tool_call(raw, tool_schemas)
|
|
362
|
+
|
|
363
|
+
# 5. Execute the tool
|
|
364
|
+
result = execute_tool(name, args)
|
|
365
|
+
|
|
366
|
+
# 6. Compact the response
|
|
367
|
+
compacted, _ = compact_tool_response(name, result, max_tokens=200)
|
|
368
|
+
|
|
369
|
+
# 7. Record execution
|
|
370
|
+
session.record_execution(ExecutionRecord(
|
|
371
|
+
timestamp=now(), tool_name=name, arguments=args,
|
|
372
|
+
status="completed", result=compacted, confidence=score.overall,
|
|
373
|
+
))
|
|
374
|
+
|
|
375
|
+
# 8. Feed back to model
|
|
376
|
+
messages.append({"role": "tool", "content": compacted})
|
|
377
|
+
```
|
|
378
|
+
|
|
379
|
+
## Research
|
|
380
|
+
|
|
381
|
+
This harness is informed by:
|
|
382
|
+
|
|
383
|
+
- **Cho et al. (2026)** — "It's Not the Size: Harness Design Determines Operational Stability in Small Language Models" (arXiv:2605.12129). Found that a 4-stage scaffold takes 2B models from 58% to 95% task success.
|
|
384
|
+
- **ManiFreeBird (2026)** — Deterministic tool repair eliminates retry loops. Key insight: "structural failures are not reasoning failures."
|
|
385
|
+
- **Gorilla (Patil et al., 2023)** — Tool retrieval reduces selection hallucination. Show 3–5 relevant tools, not all.
|
|
386
|
+
- **NVIDIA (2025)** — SLM-first routing with confidence-based cloud escalation keeps 80–90% of steps local.
|
|
387
|
+
|
|
388
|
+
## Pydantic-deep integration (optional)
|
|
389
|
+
|
|
390
|
+
For full agent capabilities (context compaction, subagents, skills, memory, planning):
|
|
391
|
+
|
|
392
|
+
```bash
|
|
393
|
+
pip install "small-model-harness[pydantic-deep]"
|
|
394
|
+
```
|
|
395
|
+
|
|
396
|
+
```python
|
|
397
|
+
from small_model_harness.pydantic_deep_integration import (
|
|
398
|
+
build_small_model_agent,
|
|
399
|
+
run_with_harness,
|
|
400
|
+
)
|
|
401
|
+
|
|
402
|
+
result = build_small_model_agent(
|
|
403
|
+
model_url="http://localhost:8080/v1",
|
|
404
|
+
model_name="qwen3.5-4b",
|
|
405
|
+
n_ctx=4096,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
run_result = await run_with_harness(result, "Plan a 3-step audio mixing task")
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## Running tests
|
|
412
|
+
|
|
413
|
+
```bash
|
|
414
|
+
pip install "small-model-harness[dev]"
|
|
415
|
+
pytest # Run all 142 tests
|
|
416
|
+
pytest tests/test_harness.py # Core harness tests only
|
|
417
|
+
pytest -v # Verbose output
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
## Contributing
|
|
421
|
+
|
|
422
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
423
|
+
|
|
424
|
+
## License
|
|
425
|
+
|
|
426
|
+
MIT — see [LICENSE](LICENSE).
|