A decorator that verifies your code actually does what its docstring says, using LLMs
Project description
akshually
A Python decorator that uses LLMs to verify your code actually does what its docstring claims.
DocMismatchError: akshually, 1 function(s) don't do what their docstrings say:
- calculate_average (math_utils.py:12): The docstring says it returns the average,
but the code returns the sum without dividing by the length.
Installation
# OpenAI
pip install "akshually[openai]"
# Anthropic
pip install "akshually[anthropic]"
# Both
pip install "akshually[all]"
Quick start
import akshually
akshually.configure(provider="openai", api_key="sk-...")
@akshually.verify
def double(x: int) -> int:
"""Return x multiplied by two."""
return x * 2
akshually.check() # passes — code matches the docstring
If the code doesn't match the docstring:
@akshually.verify
def calculate_average(numbers: list) -> float:
"""Return the average of the list."""
return sum(numbers) # oops, forgot to divide
akshually.check()
# DocMismatchError: akshually, 1 function(s) don't do what their docstrings say:
# - calculate_average (math_utils.py:8): The docstring says it returns the average
# but the code only returns the sum without dividing by len(numbers).
How it works
@akshually.verifyregisters the function in a pending queue. No LLM call yet.akshually.check()makes one LLM call with all registered functions batched together.- The LLM compares each function's implementation against its docstring.
- Any mismatches raise a single
DocMismatchErrorlisting every failing function with its file path, line number, and a plain-English explanation. - Results are cached by source hash — unchanged functions are skipped on subsequent runs.
Examples
Multiple functions — one LLM call
import akshually
akshually.configure(provider="anthropic", api_key="sk-ant-...")
@akshually.verify
def clamp(value: float, lo: float, hi: float) -> float:
"""Return value clamped to the range [lo, hi]."""
return max(lo, min(hi, value))
@akshually.verify
def is_palindrome(s: str) -> bool:
"""Return True if the string reads the same forwards and backwards."""
return s == s[::-1]
@akshually.verify
def fahrenheit_to_celsius(f: float) -> float:
"""Convert a Fahrenheit temperature to Celsius."""
return (f - 32) * 5 / 9
akshually.check() # one API call checks all three
Warn instead of raise
Use on_mismatch="warn" for functions where a mismatch should be surfaced but not fatal:
@akshually.verify(on_mismatch="warn")
def experimental_sort(items: list) -> list:
"""Return items sorted in ascending order using a novel algorithm."""
...
akshually.check()
# UserWarning: akshually: 'experimental_sort' may not match its docstring: ...
You can mix both modes freely — check() will emit warnings for "warn" functions and raise for "raise" functions in the same call.
Class decorator
@akshually.verify_class applies verification to every method on a class that has a docstring. Methods without docstrings are silently skipped.
@akshually.verify_class
class Statistics:
def median(self, numbers: list) -> float:
"""Return the median value of the list."""
sorted_nums = sorted(numbers)
n = len(sorted_nums)
mid = n // 2
return sorted_nums[mid] if n % 2 else (sorted_nums[mid - 1] + sorted_nums[mid]) / 2
def mode(self, numbers: list):
"""Return the most frequently occurring value in the list."""
from collections import Counter
return Counter(numbers).most_common(1)[0][0]
def _private_helper(self):
# no docstring — skipped automatically
pass
akshually.check()
Caching
Results are cached at ~/.cache/akshually/results.json keyed by source hash. If a function's source hasn't changed since the last check(), it won't be sent to the LLM again.
akshually.check() # uses cache (default)
akshually.check(use_cache=False) # always calls the LLM
akshually.clear_cache() # wipe all cached results
Using a custom model
akshually.configure(
provider="openai",
api_key="sk-...",
model="gpt-4-turbo",
)
In a test suite
# tests/test_docstrings.py
import os
import akshually
import mypackage # importing this registers all @akshually.verify functions
def test_docstrings_match_implementation():
akshually.configure(provider="openai", api_key=os.environ["OPENAI_API_KEY"])
akshually.check()
Or use the built-in pytest fixture:
# conftest.py
import os
import akshually
import mypackage
akshually.configure(provider="openai", api_key=os.environ["OPENAI_API_KEY"])
pytest_plugins = ["akshually.pytest_plugin"]
# tests/test_docstrings.py
def test_docstrings(akshually_check):
pass # the fixture calls akshually.check() automatically
CLI
Check Python files or directories without modifying any source code:
# Check a single file
akshually check mypackage/utils.py
# Check an entire directory (recursive)
akshually check mypackage/
# Check multiple targets
akshually check mypackage/utils.py mypackage/models.py
# Specify provider explicitly
akshually check mypackage/ --provider openai --api-key sk-...
# Skip the cache
akshually check mypackage/ --no-cache
The CLI auto-detects your provider from environment variables (OPENAI_API_KEY or ANTHROPIC_API_KEY) so you typically don't need any flags:
export OPENAI_API_KEY=sk-...
akshually check src/
Exits with code 1 if any mismatch is found — suitable for CI pipelines.
Configuration
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
provider |
"openai" | "anthropic" |
Yes | — | LLM provider to use |
api_key |
str |
Yes | — | Your API key |
model |
str |
No | gpt-4o / claude-sonnet-4-6 |
Override the default model |
Error reference
| Exception | When it's raised |
|---|---|
DocMismatchError |
One or more "raise" functions don't match their docstrings |
AksuallyConfigError |
check() called before configure(), or missing API key |
ValueError |
@akshually.verify applied to a function with no docstring |
Contributing
git clone https://github.com/example/akshually
cd akshually
pip install -e ".[all,dev]"
pytest
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file akshually-0.1.0.tar.gz.
File metadata
- Download URL: akshually-0.1.0.tar.gz
- Upload date:
- Size: 14.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: Hatch/1.16.5 cpython/3.10.12 HTTPX/0.28.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ed97fa2d6669c80b6ae30bf6289846bf1c791dc0923814222db7c47ba516b0e6
|
|
| MD5 |
4c8202c46c28fcdda427cc85c3063cbc
|
|
| BLAKE2b-256 |
b7718bb3f18753ae546b5bf329054cd202db125986557dd0cbb834e93f114e5b
|
File details
Details for the file akshually-0.1.0-py3-none-any.whl.
File metadata
- Download URL: akshually-0.1.0-py3-none-any.whl
- Upload date:
- Size: 17.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: Hatch/1.16.5 cpython/3.10.12 HTTPX/0.28.1
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
bf1405724c622ce497658236d2cc9e504f0e4de265daa1f11a2421b7de75611a
|
|
| MD5 |
35b8f481df4025610bd3c83b5c0c0c1e
|
|
| BLAKE2b-256 |
d076a97cb99cf4c26085b9f4915e0a8eaefdd556c2a8036f16b20ec60587e080
|