quickthink 0.2.1__tar.gz → 0.2.2__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 (34) hide show
  1. {quickthink-0.2.1 → quickthink-0.2.2}/PKG-INFO +21 -12
  2. {quickthink-0.2.1 → quickthink-0.2.2}/README.md +19 -10
  3. {quickthink-0.2.1 → quickthink-0.2.2}/pyproject.toml +2 -2
  4. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink/cli.py +77 -31
  5. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink/engine.py +71 -7
  6. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink.egg-info/PKG-INFO +21 -12
  7. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink.egg-info/SOURCES.txt +1 -0
  8. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink.egg-info/requires.txt +0 -1
  9. quickthink-0.2.2/tests/test_cli_offline.py +118 -0
  10. {quickthink-0.2.1 → quickthink-0.2.2}/LICENSE +0 -0
  11. {quickthink-0.2.1 → quickthink-0.2.2}/setup.cfg +0 -0
  12. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink/__init__.py +0 -0
  13. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink/config.py +0 -0
  14. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink/inline_protocol.py +0 -0
  15. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink/ollama_client.py +0 -0
  16. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink/plan_grammar.py +0 -0
  17. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink/prompts.py +0 -0
  18. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink/routing.py +0 -0
  19. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink/ui_server.py +0 -0
  20. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink.egg-info/dependency_links.txt +0 -0
  21. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink.egg-info/entry_points.txt +0 -0
  22. {quickthink-0.2.1 → quickthink-0.2.2}/src/quickthink.egg-info/top_level.txt +0 -0
  23. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_cli_lane_policy.py +0 -0
  24. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_cli_version.py +0 -0
  25. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_engine_lane_policy.py +0 -0
  26. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_eval_harness_safety.py +0 -0
  27. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_inline_protocol.py +0 -0
  28. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_make_gate_decision.py +0 -0
  29. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_plan_grammar.py +0 -0
  30. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_presets.py +0 -0
  31. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_prompts.py +0 -0
  32. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_routing.py +0 -0
  33. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_ui_lane_policy.py +0 -0
  34. {quickthink-0.2.1 → quickthink-0.2.2}/tests/test_ui_server_paths.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickthink
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Compressed planning scaffold for local LLMs — latency-aware routing and structured output reliability
5
5
  Author-email: Rolando Bosch <roli@hermes-labs.ai>
6
6
  License-Expression: Apache-2.0
@@ -17,13 +17,13 @@ Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
20
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
22
  Requires-Python: >=3.9
22
23
  Description-Content-Type: text/markdown
23
24
  License-File: LICENSE
24
25
  Requires-Dist: httpx>=0.27.0
25
26
  Requires-Dist: typer>=0.12.0
26
- Requires-Dist: pydantic>=2.7.0
27
27
  Provides-Extra: dev
28
28
  Requires-Dist: pytest>=8.2.0; extra == "dev"
29
29
  Requires-Dist: ruff>=0.4.0; extra == "dev"
@@ -92,8 +92,8 @@ Small/local models are fast but often underperform on multi-step tasks.
92
92
  Prerequisite: install and start [Ollama](https://ollama.com/) locally.
93
93
 
94
94
  ```bash
95
- # 1) Before the first PyPI publication, install the current source
96
- python -m pip install "quickthink @ git+https://github.com/hermes-labs-ai/quickthink.git"
95
+ # 1) Install the released package
96
+ python -m pip install "quickthink==0.2.2"
97
97
 
98
98
  # 2) Pull one supported model
99
99
  ollama pull qwen2.5:1.5b
@@ -104,12 +104,6 @@ quickthink ask "Give me a 3-step plan to learn SQL basics" --model qwen2.5:1.5b
104
104
 
105
105
  If this command works, your local setup is ready.
106
106
 
107
- After `v0.2.1` has been published to PyPI, install the release instead:
108
-
109
- ```bash
110
- python -m pip install "quickthink==0.2.1"
111
- ```
112
-
113
107
  For development, clone the repository and install the editable development extras:
114
108
 
115
109
  ```bash
@@ -199,6 +193,18 @@ Show routing diagnostics:
199
193
  quickthink ask "Design a robust parser with tradeoffs and a JSON output schema" --show-route --show-plan
200
194
  ```
201
195
 
196
+ Skip planning entirely (`direct` mode):
197
+
198
+ ```bash
199
+ quickthink ask "What is the capital of France?" --mode direct
200
+ ```
201
+
202
+ Inspect routing and the exact prompt(s) without calling Ollama (`--dry-run` works offline):
203
+
204
+ ```bash
205
+ quickthink ask "Design a retry strategy for a flaky payments API: compare exponential backoff versus a circuit breaker, list the tradeoffs, and return a JSON schema for the config" --mode two_pass --dry-run
206
+ ```
207
+
202
208
  Optional continuity hint (tiny, off by default):
203
209
 
204
210
  ```bash
@@ -351,8 +357,9 @@ Test:
351
357
  PYTHONPATH=src .venv/bin/pytest -q
352
358
  ```
353
359
 
354
- Lint (basic syntax/import sanity):
360
+ Lint (same commands as CI):
355
361
  ```bash
362
+ .venv/bin/ruff check src/ tests/ scripts/
356
363
  python -m compileall src tests scripts
357
364
  ```
358
365
 
@@ -384,4 +391,6 @@ Apache-2.0
384
391
 
385
392
  ## About Hermes Labs
386
393
 
387
- Hermes Labs is an independent AI-reliability lab building open-source tools that catch silent failure modes in production AI. More at [hermes-labs.ai](https://hermes-labs.ai).
394
+ [Hermes Labs](https://hermes-labs.ai) is an AI reliability engineering studio for product and engineering teams shipping production agents and LLM applications. We find the structural AI failures standard evals miss, then harden retrieval, memory, agents, and the language layers around production AI systems with runtime controls and defensible evidence.
395
+
396
+ Browse the [open-source catalog](https://hermes-labs.ai/open-source) or contact [roli@hermes-labs.ai](mailto:roli@hermes-labs.ai).
@@ -61,8 +61,8 @@ Small/local models are fast but often underperform on multi-step tasks.
61
61
  Prerequisite: install and start [Ollama](https://ollama.com/) locally.
62
62
 
63
63
  ```bash
64
- # 1) Before the first PyPI publication, install the current source
65
- python -m pip install "quickthink @ git+https://github.com/hermes-labs-ai/quickthink.git"
64
+ # 1) Install the released package
65
+ python -m pip install "quickthink==0.2.2"
66
66
 
67
67
  # 2) Pull one supported model
68
68
  ollama pull qwen2.5:1.5b
@@ -73,12 +73,6 @@ quickthink ask "Give me a 3-step plan to learn SQL basics" --model qwen2.5:1.5b
73
73
 
74
74
  If this command works, your local setup is ready.
75
75
 
76
- After `v0.2.1` has been published to PyPI, install the release instead:
77
-
78
- ```bash
79
- python -m pip install "quickthink==0.2.1"
80
- ```
81
-
82
76
  For development, clone the repository and install the editable development extras:
83
77
 
84
78
  ```bash
@@ -168,6 +162,18 @@ Show routing diagnostics:
168
162
  quickthink ask "Design a robust parser with tradeoffs and a JSON output schema" --show-route --show-plan
169
163
  ```
170
164
 
165
+ Skip planning entirely (`direct` mode):
166
+
167
+ ```bash
168
+ quickthink ask "What is the capital of France?" --mode direct
169
+ ```
170
+
171
+ Inspect routing and the exact prompt(s) without calling Ollama (`--dry-run` works offline):
172
+
173
+ ```bash
174
+ quickthink ask "Design a retry strategy for a flaky payments API: compare exponential backoff versus a circuit breaker, list the tradeoffs, and return a JSON schema for the config" --mode two_pass --dry-run
175
+ ```
176
+
171
177
  Optional continuity hint (tiny, off by default):
172
178
 
173
179
  ```bash
@@ -320,8 +326,9 @@ Test:
320
326
  PYTHONPATH=src .venv/bin/pytest -q
321
327
  ```
322
328
 
323
- Lint (basic syntax/import sanity):
329
+ Lint (same commands as CI):
324
330
  ```bash
331
+ .venv/bin/ruff check src/ tests/ scripts/
325
332
  python -m compileall src tests scripts
326
333
  ```
327
334
 
@@ -353,4 +360,6 @@ Apache-2.0
353
360
 
354
361
  ## About Hermes Labs
355
362
 
356
- Hermes Labs is an independent AI-reliability lab building open-source tools that catch silent failure modes in production AI. More at [hermes-labs.ai](https://hermes-labs.ai).
363
+ [Hermes Labs](https://hermes-labs.ai) is an AI reliability engineering studio for product and engineering teams shipping production agents and LLM applications. We find the structural AI failures standard evals miss, then harden retrieval, memory, agents, and the language layers around production AI systems with runtime controls and defensible evidence.
364
+
365
+ Browse the [open-source catalog](https://hermes-labs.ai/open-source) or contact [roli@hermes-labs.ai](mailto:roli@hermes-labs.ai).
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "quickthink"
3
- version = "0.2.1"
3
+ version = "0.2.2"
4
4
  description = "Compressed planning scaffold for local LLMs — latency-aware routing and structured output reliability"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -29,12 +29,12 @@ classifiers = [
29
29
  "Programming Language :: Python :: 3.10",
30
30
  "Programming Language :: Python :: 3.11",
31
31
  "Programming Language :: Python :: 3.12",
32
+ "Programming Language :: Python :: 3.13",
32
33
  "Topic :: Software Development :: Libraries :: Python Modules",
33
34
  ]
34
35
  dependencies = [
35
36
  "httpx>=0.27.0",
36
37
  "typer>=0.12.0",
37
- "pydantic>=2.7.0",
38
38
  ]
39
39
 
40
40
  [project.urls]
@@ -4,8 +4,9 @@ import json
4
4
  from datetime import datetime, timezone
5
5
  from importlib.metadata import version as package_version
6
6
  from pathlib import Path
7
- from typing import Optional
7
+ from typing import NoReturn, Optional
8
8
 
9
+ import httpx
9
10
  import typer
10
11
 
11
12
  from .config import MODEL_PROFILES, PRESET_PROFILES, SUPPORTED_MODELS, QuickThinkConfig
@@ -14,6 +15,8 @@ from .ui_server import serve_ui
14
15
 
15
16
  app = typer.Typer(help="Compressed planning scaffold for local LLMs")
16
17
 
18
+ MODES = ("lite", "two_pass", "direct")
19
+
17
20
 
18
21
  def _version_callback(value: bool) -> None:
19
22
  if value:
@@ -57,7 +60,7 @@ def ask(
57
60
  prompt: str = typer.Argument(..., help="User prompt"),
58
61
  model: str = typer.Option("qwen2.5:1.5b", help="Ollama model"),
59
62
  ollama_url: str = typer.Option("http://localhost:11434", help="Ollama base URL"),
60
- mode: str = typer.Option("lite", help="Execution mode: lite or two_pass"),
63
+ mode: str = typer.Option("lite", help="Execution mode: lite, two_pass, or direct"),
61
64
  preset: str = typer.Option("balanced", help="Preset profile: fast, balanced, strict"),
62
65
  show_plan: bool = typer.Option(False, help="Show compressed plan in terminal output"),
63
66
  show_route: bool = typer.Option(False, help="Show routing diagnostics"),
@@ -65,9 +68,14 @@ def ask(
65
68
  bypass_short_prompts: bool = typer.Option(True, help="Skip plan stage for short prompts"),
66
69
  continuity_hint: Optional[str] = typer.Option(None, help="Optional tiny continuity hint"),
67
70
  lane_policy: str = typer.Option("default", help="Lane policy: default or strict_safe"),
71
+ dry_run: bool = typer.Option(
72
+ False,
73
+ "--dry-run",
74
+ help="Resolve routing and print the prompt(s) that would be sent to Ollama, without calling it",
75
+ ),
68
76
  ) -> None:
69
- if mode not in {"lite", "two_pass"}:
70
- raise typer.BadParameter("mode must be 'lite' or 'two_pass'")
77
+ if mode not in MODES:
78
+ raise typer.BadParameter("mode must be one of: lite, two_pass, direct")
71
79
  if preset not in PRESET_PROFILES:
72
80
  raise typer.BadParameter("preset must be one of: fast, balanced, strict")
73
81
  if lane_policy not in {"default", "strict_safe"}:
@@ -80,7 +88,21 @@ def ask(
80
88
  config.lane_policy = lane_policy
81
89
  engine = QuickThinkEngine(config)
82
90
 
83
- result = engine.run(prompt)
91
+ if dry_run:
92
+ preview = engine.preview(prompt)
93
+ typer.echo(
94
+ f"[route] mode={preview.mode} bypassed={preview.bypassed} score={preview.route_score} "
95
+ f"plan_budget={preview.selected_plan_budget} model_calls={preview.model_calls} model={model}"
96
+ )
97
+ for stage, text in preview.prompts:
98
+ typer.echo(f"[prompt:{stage}]")
99
+ typer.echo(text)
100
+ return
101
+
102
+ try:
103
+ result = engine.run(prompt)
104
+ except httpx.HTTPError as exc:
105
+ _ollama_error(exc, ollama_url=ollama_url, model=model)
84
106
 
85
107
  if show_route:
86
108
  typer.echo(
@@ -117,6 +139,42 @@ def ask(
117
139
  )
118
140
 
119
141
 
142
+ def _ollama_error(exc: httpx.HTTPError, *, ollama_url: str, model: str) -> NoReturn:
143
+ typer.echo(
144
+ f"error: Ollama request to {ollama_url} failed ({exc.__class__.__name__}: {exc}).\n"
145
+ f"Start Ollama with `ollama serve` and make sure the model is available with `ollama pull {model}`.",
146
+ err=True,
147
+ )
148
+ raise typer.Exit(code=2)
149
+
150
+
151
+ def _bench_modes(
152
+ *,
153
+ prompt: str,
154
+ model: str,
155
+ ollama_url: str,
156
+ runs: int,
157
+ preset: str,
158
+ lane_policy: str,
159
+ lite_latencies: list[float],
160
+ two_pass_latencies: list[float],
161
+ direct_latencies: list[float],
162
+ ) -> None:
163
+ for mode, latencies in (("lite", lite_latencies), ("two_pass", two_pass_latencies), ("direct", direct_latencies)):
164
+ config = QuickThinkConfig.with_model_profile(model=model, ollama_url=ollama_url)
165
+ config.apply_preset(preset)
166
+ config.mode = mode
167
+ # Benchmark the configured mode itself: without this, prompts below the preset's
168
+ # bypass threshold or complexity score would silently measure the direct path under
169
+ # a lite/two_pass label. `strict_safe` keeps its own strict-format bypass.
170
+ config.bypass_short_prompts = False
171
+ config.adaptive_routing = False
172
+ config.lane_policy = lane_policy
173
+ engine = QuickThinkEngine(config)
174
+ for _ in range(runs):
175
+ latencies.append(engine.run(prompt).total_latency_ms)
176
+
177
+
120
178
  @app.command()
121
179
  def bench(
122
180
  prompt: str = typer.Argument(..., help="Benchmark prompt"),
@@ -134,32 +192,20 @@ def bench(
134
192
  two_pass_latencies: list[float] = []
135
193
  direct_latencies: list[float] = []
136
194
 
137
- config_lite = QuickThinkConfig.with_model_profile(model=model, ollama_url=ollama_url)
138
- config_lite.apply_preset(preset)
139
- config_lite.mode = "lite"
140
- config_lite.lane_policy = lane_policy
141
- engine_lite = QuickThinkEngine(config_lite)
142
- for _ in range(runs):
143
- lite_latencies.append(engine_lite.run(prompt).total_latency_ms)
144
-
145
- config_two_pass = QuickThinkConfig.with_model_profile(model=model, ollama_url=ollama_url)
146
- config_two_pass.apply_preset(preset)
147
- config_two_pass.mode = "two_pass"
148
- config_two_pass.lane_policy = lane_policy
149
- engine_two_pass = QuickThinkEngine(config_two_pass)
150
- for _ in range(runs):
151
- two_pass_latencies.append(engine_two_pass.run(prompt).total_latency_ms)
152
-
153
- config_direct = QuickThinkConfig.with_model_profile(model=model, ollama_url=ollama_url)
154
- config_direct.apply_preset(preset)
155
- config_direct.mode = "lite"
156
- config_direct.lane_policy = lane_policy
157
- config_direct.bypass_short_prompts = True
158
- config_direct.adaptive_routing = False
159
- config_direct.bypass_char_threshold = 100_000
160
- engine_direct = QuickThinkEngine(config_direct)
161
- for _ in range(runs):
162
- direct_latencies.append(engine_direct.run(prompt).total_latency_ms)
195
+ try:
196
+ _bench_modes(
197
+ prompt=prompt,
198
+ model=model,
199
+ ollama_url=ollama_url,
200
+ runs=runs,
201
+ preset=preset,
202
+ lane_policy=lane_policy,
203
+ lite_latencies=lite_latencies,
204
+ two_pass_latencies=two_pass_latencies,
205
+ direct_latencies=direct_latencies,
206
+ )
207
+ except httpx.HTTPError as exc:
208
+ _ollama_error(exc, ollama_url=ollama_url, model=model)
163
209
 
164
210
  avg_lite = sum(lite_latencies) / len(lite_latencies)
165
211
  avg_two_pass = sum(two_pass_latencies) / len(two_pass_latencies)
@@ -33,23 +33,87 @@ class QuickThinkResult:
33
33
  return self.plan_latency_ms + self.answer_latency_ms
34
34
 
35
35
 
36
+ @dataclass
37
+ class QuickThinkPreview:
38
+ """Routing decision plus the prompt(s) that would be sent, produced without a model call."""
39
+
40
+ mode: str
41
+ bypassed: bool
42
+ route_score: int
43
+ selected_plan_budget: int
44
+ prompts: list[tuple[str, str]]
45
+ model_calls_min: int
46
+ model_calls_max: int
47
+
48
+ @property
49
+ def model_calls(self) -> str:
50
+ """Human-readable call count, e.g. ``"1"`` or ``"2-3"`` when a plan repair call may occur."""
51
+ if self.model_calls_min == self.model_calls_max:
52
+ return str(self.model_calls_min)
53
+ return f"{self.model_calls_min}-{self.model_calls_max}"
54
+
55
+
36
56
  class QuickThinkEngine:
37
57
  def __init__(self, config: QuickThinkConfig) -> None:
38
58
  self.config = config
39
59
  self.client = OllamaClient(config.ollama_url, timeout_s=config.request_timeout_s)
40
60
 
41
- def run(self, prompt: str) -> QuickThinkResult:
61
+ def _resolve_route(self, prompt: str) -> tuple[bool, int, int]:
62
+ """Return (bypass, route_score, selected_budget) without contacting the model."""
42
63
  if self.config.lane_policy == "strict_safe" and infer_task_class(prompt) == "strict_format":
43
- return self._run_direct(prompt=prompt, route_score=-1, selected_budget=self.config.min_plan_budget_tokens)
64
+ return True, -1, self.config.min_plan_budget_tokens
65
+ return should_bypass(prompt, self.config)
66
+
67
+ def preview(self, prompt: str) -> QuickThinkPreview:
68
+ """Resolve routing and build the exact prompt(s) that ``run`` would send, without calling Ollama."""
69
+ bypass, route_score, selected_budget = self._resolve_route(prompt)
70
+ if bypass or self.config.mode == "direct":
71
+ return QuickThinkPreview(
72
+ mode=self.config.mode,
73
+ bypassed=True,
74
+ route_score=route_score,
75
+ selected_plan_budget=selected_budget,
76
+ prompts=[("answer", prompt)],
77
+ model_calls_min=1,
78
+ model_calls_max=1,
79
+ )
80
+ if self.config.mode == "two_pass":
81
+ # The answer prompt embeds the plan returned by the first call, so it is shown as a
82
+ # template; an invalid first plan triggers one extra repair call before the answer.
83
+ prompts = [
84
+ ("plan", make_plan_prompt(prompt, selected_budget)),
85
+ ("answer-template", make_answer_prompt(prompt, "<plan from the first call>")),
86
+ ]
87
+ calls_min, calls_max = 2, 3
88
+ else:
89
+ prompts = [
90
+ (
91
+ "plan+answer",
92
+ make_inline_plan_answer_prompt(
93
+ prompt,
94
+ selected_budget,
95
+ continuity_hint=self.config.continuity_hint,
96
+ scaffold_rules=self.config.scaffold_rules,
97
+ ),
98
+ )
99
+ ]
100
+ calls_min, calls_max = 1, 1
101
+ return QuickThinkPreview(
102
+ mode=self.config.mode,
103
+ bypassed=False,
104
+ route_score=route_score,
105
+ selected_plan_budget=selected_budget,
106
+ prompts=prompts,
107
+ model_calls_min=calls_min,
108
+ model_calls_max=calls_max,
109
+ )
44
110
 
45
- bypass, route_score, selected_budget = should_bypass(prompt, self.config)
46
- if bypass:
111
+ def run(self, prompt: str) -> QuickThinkResult:
112
+ bypass, route_score, selected_budget = self._resolve_route(prompt)
113
+ if bypass or self.config.mode == "direct":
47
114
  return self._run_direct(prompt=prompt, route_score=route_score, selected_budget=selected_budget)
48
-
49
115
  if self.config.mode == "two_pass":
50
116
  return self._run_two_pass(prompt, route_score, selected_budget)
51
- if self.config.mode == "direct":
52
- return self._run_direct(prompt=prompt, route_score=route_score, selected_budget=selected_budget)
53
117
  return self._run_lite(prompt, route_score, selected_budget)
54
118
 
55
119
  def _run_direct(self, prompt: str, route_score: int, selected_budget: int) -> QuickThinkResult:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: quickthink
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: Compressed planning scaffold for local LLMs — latency-aware routing and structured output reliability
5
5
  Author-email: Rolando Bosch <roli@hermes-labs.ai>
6
6
  License-Expression: Apache-2.0
@@ -17,13 +17,13 @@ Classifier: Programming Language :: Python :: 3.9
17
17
  Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
+ Classifier: Programming Language :: Python :: 3.13
20
21
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
21
22
  Requires-Python: >=3.9
22
23
  Description-Content-Type: text/markdown
23
24
  License-File: LICENSE
24
25
  Requires-Dist: httpx>=0.27.0
25
26
  Requires-Dist: typer>=0.12.0
26
- Requires-Dist: pydantic>=2.7.0
27
27
  Provides-Extra: dev
28
28
  Requires-Dist: pytest>=8.2.0; extra == "dev"
29
29
  Requires-Dist: ruff>=0.4.0; extra == "dev"
@@ -92,8 +92,8 @@ Small/local models are fast but often underperform on multi-step tasks.
92
92
  Prerequisite: install and start [Ollama](https://ollama.com/) locally.
93
93
 
94
94
  ```bash
95
- # 1) Before the first PyPI publication, install the current source
96
- python -m pip install "quickthink @ git+https://github.com/hermes-labs-ai/quickthink.git"
95
+ # 1) Install the released package
96
+ python -m pip install "quickthink==0.2.2"
97
97
 
98
98
  # 2) Pull one supported model
99
99
  ollama pull qwen2.5:1.5b
@@ -104,12 +104,6 @@ quickthink ask "Give me a 3-step plan to learn SQL basics" --model qwen2.5:1.5b
104
104
 
105
105
  If this command works, your local setup is ready.
106
106
 
107
- After `v0.2.1` has been published to PyPI, install the release instead:
108
-
109
- ```bash
110
- python -m pip install "quickthink==0.2.1"
111
- ```
112
-
113
107
  For development, clone the repository and install the editable development extras:
114
108
 
115
109
  ```bash
@@ -199,6 +193,18 @@ Show routing diagnostics:
199
193
  quickthink ask "Design a robust parser with tradeoffs and a JSON output schema" --show-route --show-plan
200
194
  ```
201
195
 
196
+ Skip planning entirely (`direct` mode):
197
+
198
+ ```bash
199
+ quickthink ask "What is the capital of France?" --mode direct
200
+ ```
201
+
202
+ Inspect routing and the exact prompt(s) without calling Ollama (`--dry-run` works offline):
203
+
204
+ ```bash
205
+ quickthink ask "Design a retry strategy for a flaky payments API: compare exponential backoff versus a circuit breaker, list the tradeoffs, and return a JSON schema for the config" --mode two_pass --dry-run
206
+ ```
207
+
202
208
  Optional continuity hint (tiny, off by default):
203
209
 
204
210
  ```bash
@@ -351,8 +357,9 @@ Test:
351
357
  PYTHONPATH=src .venv/bin/pytest -q
352
358
  ```
353
359
 
354
- Lint (basic syntax/import sanity):
360
+ Lint (same commands as CI):
355
361
  ```bash
362
+ .venv/bin/ruff check src/ tests/ scripts/
356
363
  python -m compileall src tests scripts
357
364
  ```
358
365
 
@@ -384,4 +391,6 @@ Apache-2.0
384
391
 
385
392
  ## About Hermes Labs
386
393
 
387
- Hermes Labs is an independent AI-reliability lab building open-source tools that catch silent failure modes in production AI. More at [hermes-labs.ai](https://hermes-labs.ai).
394
+ [Hermes Labs](https://hermes-labs.ai) is an AI reliability engineering studio for product and engineering teams shipping production agents and LLM applications. We find the structural AI failures standard evals miss, then harden retrieval, memory, agents, and the language layers around production AI systems with runtime controls and defensible evidence.
395
+
396
+ Browse the [open-source catalog](https://hermes-labs.ai/open-source) or contact [roli@hermes-labs.ai](mailto:roli@hermes-labs.ai).
@@ -18,6 +18,7 @@ src/quickthink.egg-info/entry_points.txt
18
18
  src/quickthink.egg-info/requires.txt
19
19
  src/quickthink.egg-info/top_level.txt
20
20
  tests/test_cli_lane_policy.py
21
+ tests/test_cli_offline.py
21
22
  tests/test_cli_version.py
22
23
  tests/test_engine_lane_policy.py
23
24
  tests/test_eval_harness_safety.py
@@ -1,6 +1,5 @@
1
1
  httpx>=0.27.0
2
2
  typer>=0.12.0
3
- pydantic>=2.7.0
4
3
 
5
4
  [dev]
6
5
  pytest>=8.2.0
@@ -0,0 +1,118 @@
1
+ """CLI behaviour that must work without a running Ollama server."""
2
+
3
+ from typer.testing import CliRunner
4
+
5
+ from quickthink.cli import app
6
+ from quickthink.config import QuickThinkConfig
7
+ from quickthink.engine import QuickThinkEngine
8
+
9
+ # Port 9 (discard) is closed on every CI runner, so a connection is refused immediately.
10
+ UNREACHABLE = "http://127.0.0.1:9"
11
+
12
+ # Longer than the balanced preset's 120-char bypass threshold and scores >1 on the routing regexes,
13
+ # so it takes the scaffold path instead of the direct path.
14
+ SCAFFOLD_PROMPT = (
15
+ "Design a retry strategy for a flaky payments API: compare exponential backoff versus a circuit "
16
+ "breaker, list the tradeoffs, and return a JSON schema for the config"
17
+ )
18
+
19
+
20
+ def test_help_runs_without_ollama() -> None:
21
+ result = CliRunner().invoke(app, ["--help"])
22
+ assert result.exit_code == 0
23
+ assert "ask" in result.output
24
+
25
+
26
+ def test_ask_accepts_direct_mode_and_dry_run_makes_no_network_call() -> None:
27
+ result = CliRunner().invoke(
28
+ app,
29
+ ["ask", "what is 2 + 2?", "--mode", "direct", "--ollama-url", UNREACHABLE, "--dry-run"],
30
+ )
31
+ assert result.exit_code == 0, result.output
32
+ assert "[route] mode=direct bypassed=True" in result.output
33
+ assert "model_calls=1" in result.output
34
+ assert "[prompt:answer]\nwhat is 2 + 2?" in result.output
35
+
36
+
37
+ def test_dry_run_lite_shows_scaffold_prompt() -> None:
38
+ prompt = SCAFFOLD_PROMPT
39
+ result = CliRunner().invoke(app, ["ask", prompt, "--ollama-url", UNREACHABLE, "--dry-run"])
40
+ assert result.exit_code == 0, result.output
41
+ assert "[route] mode=lite bypassed=False" in result.output
42
+ assert "[prompt:plan+answer]" in result.output
43
+ assert "Generate a compact internal plan prefix" in result.output
44
+
45
+
46
+ def test_dry_run_two_pass_lists_both_calls() -> None:
47
+ prompt = SCAFFOLD_PROMPT
48
+ result = CliRunner().invoke(
49
+ app, ["ask", prompt, "--mode", "two_pass", "--ollama-url", UNREACHABLE, "--dry-run"]
50
+ )
51
+ assert result.exit_code == 0, result.output
52
+ assert "model_calls=2-3" in result.output
53
+ assert "[prompt:plan]" in result.output
54
+ assert "[prompt:answer-template]" in result.output
55
+ assert "<plan from the first call>" in result.output
56
+
57
+
58
+ def test_ask_invalid_mode_rejected() -> None:
59
+ result = CliRunner().invoke(app, ["ask", "hello", "--mode", "turbo"])
60
+ assert result.exit_code != 0
61
+ assert "mode must be one of: lite, two_pass, direct" in result.output
62
+
63
+
64
+ def test_ask_reports_unreachable_ollama_cleanly() -> None:
65
+ result = CliRunner().invoke(app, ["ask", "hello", "--ollama-url", UNREACHABLE])
66
+ assert result.exit_code == 2
67
+ assert result.exception is None or isinstance(result.exception, SystemExit)
68
+ assert "error: Ollama request to http://127.0.0.1:9 failed" in result.output
69
+ assert "ollama serve" in result.output
70
+
71
+
72
+ def test_engine_preview_matches_run_routing(monkeypatch) -> None:
73
+ cfg = QuickThinkConfig.with_model_profile("qwen2.5:1.5b")
74
+ cfg.mode = "lite"
75
+ cfg.bypass_short_prompts = False
76
+ cfg.adaptive_routing = False
77
+ engine = QuickThinkEngine(cfg)
78
+
79
+ sent: list[str] = []
80
+
81
+ def fake_generate(**kwargs): # type: ignore[no-untyped-def]
82
+ sent.append(str(kwargs.get("prompt", "")))
83
+ return {"response": "[P]g:x;c:y;s:z;r:k\n[A]ok"}
84
+
85
+ monkeypatch.setattr(engine.client, "generate", fake_generate)
86
+
87
+ preview = engine.preview("Compare two retry strategies")
88
+ result = engine.run("Compare two retry strategies")
89
+
90
+ assert preview.bypassed is result.bypassed
91
+ assert preview.route_score == result.route_score
92
+ assert preview.selected_plan_budget == result.selected_plan_budget
93
+ assert [text for _, text in preview.prompts] == sent
94
+
95
+
96
+ def test_bench_forces_planned_modes(monkeypatch) -> None:
97
+ """A short prompt must still be measured under lite and two_pass, not bypassed to direct."""
98
+ from quickthink import cli
99
+
100
+ seen: list[tuple[str, bool]] = []
101
+
102
+ class FakeEngine:
103
+ def __init__(self, config: QuickThinkConfig) -> None:
104
+ self.config = config
105
+
106
+ def run(self, prompt: str): # type: ignore[no-untyped-def]
107
+ real = QuickThinkEngine(self.config).preview(prompt)
108
+ seen.append((self.config.mode, real.bypassed))
109
+
110
+ class R:
111
+ total_latency_ms = 1.0
112
+
113
+ return R()
114
+
115
+ monkeypatch.setattr(cli, "QuickThinkEngine", FakeEngine)
116
+ result = CliRunner().invoke(app, ["bench", "what is 2 + 2?", "--runs", "1"])
117
+ assert result.exit_code == 0, result.output
118
+ assert seen == [("lite", False), ("two_pass", False), ("direct", True)]
File without changes
File without changes