self-heal-llm 0.0.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.
- self_heal_llm-0.0.2/.github/workflows/ci.yml +37 -0
- self_heal_llm-0.0.2/.github/workflows/publish.yml +40 -0
- self_heal_llm-0.0.2/.gitignore +79 -0
- self_heal_llm-0.0.2/LICENSE +21 -0
- self_heal_llm-0.0.2/PKG-INFO +257 -0
- self_heal_llm-0.0.2/README.md +191 -0
- self_heal_llm-0.0.2/examples/basic.py +29 -0
- self_heal_llm-0.0.2/examples/extract_price.py +31 -0
- self_heal_llm-0.0.2/pyproject.toml +83 -0
- self_heal_llm-0.0.2/src/self_heal/__init__.py +42 -0
- self_heal_llm-0.0.2/src/self_heal/core.py +79 -0
- self_heal_llm-0.0.2/src/self_heal/diagnose.py +27 -0
- self_heal_llm-0.0.2/src/self_heal/llm/__init__.py +60 -0
- self_heal_llm-0.0.2/src/self_heal/llm/_claude.py +48 -0
- self_heal_llm-0.0.2/src/self_heal/llm/_gemini.py +50 -0
- self_heal_llm-0.0.2/src/self_heal/llm/_litellm.py +58 -0
- self_heal_llm-0.0.2/src/self_heal/llm/_openai.py +77 -0
- self_heal_llm-0.0.2/src/self_heal/loop.py +161 -0
- self_heal_llm-0.0.2/src/self_heal/propose.py +67 -0
- self_heal_llm-0.0.2/src/self_heal/types.py +40 -0
- self_heal_llm-0.0.2/tests/__init__.py +0 -0
- self_heal_llm-0.0.2/tests/test_core.py +155 -0
- self_heal_llm-0.0.2/tests/test_proposers.py +336 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test on Python ${{ matrix.python-version }}
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
23
|
+
uses: actions/setup-python@v5
|
|
24
|
+
with:
|
|
25
|
+
python-version: ${{ matrix.python-version }}
|
|
26
|
+
cache: pip
|
|
27
|
+
|
|
28
|
+
- name: Install package
|
|
29
|
+
run: |
|
|
30
|
+
python -m pip install --upgrade pip
|
|
31
|
+
pip install -e ".[dev]"
|
|
32
|
+
|
|
33
|
+
- name: Lint (ruff)
|
|
34
|
+
run: ruff check .
|
|
35
|
+
|
|
36
|
+
- name: Run tests
|
|
37
|
+
run: pytest -v
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
publish:
|
|
9
|
+
name: Build and publish to PyPI
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
# OIDC trusted publishing — no API tokens required.
|
|
13
|
+
# Configure a pending publisher at:
|
|
14
|
+
# https://pypi.org/manage/account/publishing/
|
|
15
|
+
# Project: self-heal
|
|
16
|
+
# Owner: Johin2
|
|
17
|
+
# Repo: self-heal
|
|
18
|
+
# Workflow: publish.yml
|
|
19
|
+
permissions:
|
|
20
|
+
id-token: write
|
|
21
|
+
|
|
22
|
+
steps:
|
|
23
|
+
- name: Checkout
|
|
24
|
+
uses: actions/checkout@v4
|
|
25
|
+
|
|
26
|
+
- name: Set up Python
|
|
27
|
+
uses: actions/setup-python@v5
|
|
28
|
+
with:
|
|
29
|
+
python-version: "3.12"
|
|
30
|
+
|
|
31
|
+
- name: Install build tooling
|
|
32
|
+
run: |
|
|
33
|
+
python -m pip install --upgrade pip
|
|
34
|
+
python -m pip install build
|
|
35
|
+
|
|
36
|
+
- name: Build distribution
|
|
37
|
+
run: python -m build
|
|
38
|
+
|
|
39
|
+
- name: Publish to PyPI
|
|
40
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# Distribution / packaging
|
|
7
|
+
.Python
|
|
8
|
+
build/
|
|
9
|
+
develop-eggs/
|
|
10
|
+
dist/
|
|
11
|
+
downloads/
|
|
12
|
+
eggs/
|
|
13
|
+
.eggs/
|
|
14
|
+
lib/
|
|
15
|
+
lib64/
|
|
16
|
+
parts/
|
|
17
|
+
sdist/
|
|
18
|
+
var/
|
|
19
|
+
wheels/
|
|
20
|
+
share/python-wheels/
|
|
21
|
+
*.egg-info/
|
|
22
|
+
.installed.cfg
|
|
23
|
+
*.egg
|
|
24
|
+
MANIFEST
|
|
25
|
+
|
|
26
|
+
# PyInstaller
|
|
27
|
+
*.manifest
|
|
28
|
+
*.spec
|
|
29
|
+
|
|
30
|
+
# Installer logs
|
|
31
|
+
pip-log.txt
|
|
32
|
+
pip-delete-this-directory.txt
|
|
33
|
+
|
|
34
|
+
# Unit test / coverage reports
|
|
35
|
+
htmlcov/
|
|
36
|
+
.tox/
|
|
37
|
+
.nox/
|
|
38
|
+
.coverage
|
|
39
|
+
.coverage.*
|
|
40
|
+
.cache
|
|
41
|
+
nosetests.xml
|
|
42
|
+
coverage.xml
|
|
43
|
+
*.cover
|
|
44
|
+
*.py,cover
|
|
45
|
+
.hypothesis/
|
|
46
|
+
.pytest_cache/
|
|
47
|
+
cover/
|
|
48
|
+
|
|
49
|
+
# Environments
|
|
50
|
+
.env
|
|
51
|
+
.env.*
|
|
52
|
+
.venv
|
|
53
|
+
env/
|
|
54
|
+
venv/
|
|
55
|
+
ENV/
|
|
56
|
+
|
|
57
|
+
# IDE
|
|
58
|
+
.vscode/
|
|
59
|
+
.idea/
|
|
60
|
+
*.swp
|
|
61
|
+
*.swo
|
|
62
|
+
*~
|
|
63
|
+
.DS_Store
|
|
64
|
+
Thumbs.db
|
|
65
|
+
|
|
66
|
+
# uv
|
|
67
|
+
.uv/
|
|
68
|
+
uv.lock
|
|
69
|
+
|
|
70
|
+
# Ruff
|
|
71
|
+
.ruff_cache/
|
|
72
|
+
|
|
73
|
+
# Jupyter
|
|
74
|
+
.ipynb_checkpoints/
|
|
75
|
+
|
|
76
|
+
# Type checkers
|
|
77
|
+
.mypy_cache/
|
|
78
|
+
.pyright/
|
|
79
|
+
.pytype/
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Johin Johny
|
|
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,257 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: self-heal-llm
|
|
3
|
+
Version: 0.0.2
|
|
4
|
+
Summary: Automatic repair for failing Python code, powered by any LLM.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Johin2/self-heal
|
|
6
|
+
Project-URL: Repository, https://github.com/Johin2/self-heal
|
|
7
|
+
Project-URL: Issues, https://github.com/Johin2/self-heal/issues
|
|
8
|
+
Author-email: Johin Johny <anandowais@gmail.com>
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 Johin Johny
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
14
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
15
|
+
in the Software without restriction, including without limitation the rights
|
|
16
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
17
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
18
|
+
furnished to do so, subject to the following conditions:
|
|
19
|
+
|
|
20
|
+
The above copyright notice and this permission notice shall be included in all
|
|
21
|
+
copies or substantial portions of the Software.
|
|
22
|
+
|
|
23
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
24
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
25
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
26
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
27
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
28
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
29
|
+
SOFTWARE.
|
|
30
|
+
License-File: LICENSE
|
|
31
|
+
Keywords: agent,ai,claude,code-repair,gemini,litellm,llm,openai,repair,self-healing
|
|
32
|
+
Classifier: Development Status :: 3 - Alpha
|
|
33
|
+
Classifier: Intended Audience :: Developers
|
|
34
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
35
|
+
Classifier: Operating System :: OS Independent
|
|
36
|
+
Classifier: Programming Language :: Python :: 3
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
41
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
42
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
43
|
+
Requires-Python: >=3.10
|
|
44
|
+
Requires-Dist: pydantic>=2.0.0
|
|
45
|
+
Provides-Extra: all
|
|
46
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'all'
|
|
47
|
+
Requires-Dist: google-genai>=0.3.0; extra == 'all'
|
|
48
|
+
Requires-Dist: litellm>=1.0.0; extra == 'all'
|
|
49
|
+
Requires-Dist: openai>=1.40.0; extra == 'all'
|
|
50
|
+
Provides-Extra: claude
|
|
51
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'claude'
|
|
52
|
+
Provides-Extra: dev
|
|
53
|
+
Requires-Dist: anthropic>=0.40.0; extra == 'dev'
|
|
54
|
+
Requires-Dist: google-genai>=0.3.0; extra == 'dev'
|
|
55
|
+
Requires-Dist: litellm>=1.0.0; extra == 'dev'
|
|
56
|
+
Requires-Dist: openai>=1.40.0; extra == 'dev'
|
|
57
|
+
Requires-Dist: pytest>=8.0.0; extra == 'dev'
|
|
58
|
+
Requires-Dist: ruff>=0.6.0; extra == 'dev'
|
|
59
|
+
Provides-Extra: gemini
|
|
60
|
+
Requires-Dist: google-genai>=0.3.0; extra == 'gemini'
|
|
61
|
+
Provides-Extra: litellm
|
|
62
|
+
Requires-Dist: litellm>=1.0.0; extra == 'litellm'
|
|
63
|
+
Provides-Extra: openai
|
|
64
|
+
Requires-Dist: openai>=1.40.0; extra == 'openai'
|
|
65
|
+
Description-Content-Type: text/markdown
|
|
66
|
+
|
|
67
|
+
# self-heal
|
|
68
|
+
|
|
69
|
+
[](https://github.com/Johin2/self-heal/actions/workflows/ci.yml)
|
|
70
|
+
[](https://pypi.org/project/self-heal-llm/)
|
|
71
|
+
[](https://pypi.org/project/self-heal-llm/)
|
|
72
|
+
[](LICENSE)
|
|
73
|
+
|
|
74
|
+
> Automatic repair for failing Python code, powered by any LLM.
|
|
75
|
+
|
|
76
|
+
When a function fails, `self-heal` catches the exception, analyzes it with an LLM, proposes a repaired version, and retries. One decorator. Works with Claude, OpenAI, Gemini, and 100+ other providers.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
from self_heal import repair
|
|
80
|
+
|
|
81
|
+
@repair(max_attempts=3)
|
|
82
|
+
def extract_price(text: str) -> float:
|
|
83
|
+
# Naive: only handles "$X.YY"
|
|
84
|
+
return float(text.replace("$", ""))
|
|
85
|
+
|
|
86
|
+
print(extract_price("$12.99")) # 12.99
|
|
87
|
+
print(extract_price("₹1,299")) # 1299.0 (repaired)
|
|
88
|
+
print(extract_price("€5,49")) # 5.49 (repaired)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Install
|
|
92
|
+
|
|
93
|
+
`self-heal` ships with a Protocol and several optional adapters. Install the adapter(s) you want:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
pip install 'self-heal-llm[claude]' # Anthropic Claude (default)
|
|
97
|
+
pip install 'self-heal-llm[openai]' # OpenAI + OpenAI-compatible endpoints
|
|
98
|
+
pip install 'self-heal-llm[gemini]' # Google Gemini
|
|
99
|
+
pip install 'self-heal-llm[litellm]' # 100+ providers via LiteLLM
|
|
100
|
+
pip install 'self-heal-llm[all]' # everything
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> PyPI distribution name is `self-heal-llm` (the short name `self-heal` was blocked by PyPI's similarity check with an unrelated package). The Python import stays `from self_heal import ...`.
|
|
104
|
+
|
|
105
|
+
## Provider support
|
|
106
|
+
|
|
107
|
+
| Adapter | Covers |
|
|
108
|
+
|---|---|
|
|
109
|
+
| `ClaudeProposer` | Anthropic Claude (native SDK) |
|
|
110
|
+
| `OpenAIProposer` | OpenAI + **any OpenAI-compatible endpoint** (OpenRouter, Together, Groq, Fireworks, Anyscale, Perplexity, xAI, DeepSeek, Azure, Ollama, LM Studio, vLLM, llama.cpp server, ...) |
|
|
111
|
+
| `GeminiProposer` | Google Gemini (native SDK) |
|
|
112
|
+
| `LiteLLMProposer` | 100+ providers via LiteLLM (Bedrock, Vertex, Cohere, Mistral, ...) |
|
|
113
|
+
|
|
114
|
+
## Using different providers
|
|
115
|
+
|
|
116
|
+
**Claude (default):**
|
|
117
|
+
```python
|
|
118
|
+
from self_heal import repair
|
|
119
|
+
|
|
120
|
+
@repair() # uses ClaudeProposer under the hood
|
|
121
|
+
def my_fn(...): ...
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
**OpenAI:**
|
|
125
|
+
```python
|
|
126
|
+
from self_heal import repair
|
|
127
|
+
from self_heal.llm import OpenAIProposer
|
|
128
|
+
|
|
129
|
+
@repair(proposer=OpenAIProposer(model="gpt-5"))
|
|
130
|
+
def my_fn(...): ...
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
**Gemini:**
|
|
134
|
+
```python
|
|
135
|
+
from self_heal import repair
|
|
136
|
+
from self_heal.llm import GeminiProposer
|
|
137
|
+
|
|
138
|
+
@repair(proposer=GeminiProposer(model="gemini-2.5-pro"))
|
|
139
|
+
def my_fn(...): ...
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Any OpenAI-compatible endpoint (OpenRouter, Groq, Ollama, ...):**
|
|
143
|
+
```python
|
|
144
|
+
from self_heal.llm import OpenAIProposer
|
|
145
|
+
|
|
146
|
+
# OpenRouter — hundreds of models through one key
|
|
147
|
+
proposer = OpenAIProposer(
|
|
148
|
+
model="google/gemini-2.5-pro",
|
|
149
|
+
base_url="https://openrouter.ai/api/v1",
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Groq — fast inference
|
|
153
|
+
proposer = OpenAIProposer(
|
|
154
|
+
model="llama-3.3-70b-versatile",
|
|
155
|
+
base_url="https://api.groq.com/openai/v1",
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# Local Ollama
|
|
159
|
+
proposer = OpenAIProposer(
|
|
160
|
+
model="llama3.3",
|
|
161
|
+
base_url="http://localhost:11434/v1",
|
|
162
|
+
api_key="ollama",
|
|
163
|
+
)
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
**LiteLLM catch-all (100+ providers):**
|
|
167
|
+
```python
|
|
168
|
+
from self_heal.llm import LiteLLMProposer
|
|
169
|
+
|
|
170
|
+
proposer = LiteLLMProposer(model="anthropic/claude-sonnet-4-6")
|
|
171
|
+
proposer = LiteLLMProposer(model="bedrock/anthropic.claude-3-5-sonnet")
|
|
172
|
+
proposer = LiteLLMProposer(model="vertex_ai/gemini-2.5-pro")
|
|
173
|
+
proposer = LiteLLMProposer(model="cohere/command-r-plus")
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
## Why this exists
|
|
177
|
+
|
|
178
|
+
AI coding agents fail on a lot of real tasks. The industry's current answer is "retry and hope." That's not a strategy.
|
|
179
|
+
|
|
180
|
+
`self-heal` treats repair as a first-class primitive: diagnose the failure, propose a targeted fix, verify, retry. A thin library you can wrap around any Python function or agent tool.
|
|
181
|
+
|
|
182
|
+
Built on ongoing code-repair research (RepairBench, NeurIPS 2026).
|
|
183
|
+
|
|
184
|
+
## How it works
|
|
185
|
+
|
|
186
|
+
1. **Catch** the exception and capture inputs, traceback, and failure type.
|
|
187
|
+
2. **Classify** the failure (validation, exception, assertion).
|
|
188
|
+
3. **Propose** a repaired function via LLM with a failure-aware prompt.
|
|
189
|
+
4. **Recompile** the proposed function into the running process.
|
|
190
|
+
5. **Retry** with the same inputs.
|
|
191
|
+
|
|
192
|
+
All within a single decorator boundary.
|
|
193
|
+
|
|
194
|
+
## API
|
|
195
|
+
|
|
196
|
+
**Decorator:**
|
|
197
|
+
```python
|
|
198
|
+
from self_heal import repair
|
|
199
|
+
|
|
200
|
+
@repair(max_attempts=3, model="claude-sonnet-4-6", verbose=True)
|
|
201
|
+
def my_tool(x): ...
|
|
202
|
+
|
|
203
|
+
my_tool(42)
|
|
204
|
+
my_tool.last_repair # -> RepairResult with full attempt history
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Loop (for advanced use):**
|
|
208
|
+
```python
|
|
209
|
+
from self_heal import RepairLoop
|
|
210
|
+
|
|
211
|
+
loop = RepairLoop(max_attempts=5, verbose=True)
|
|
212
|
+
result = loop.run(my_tool, args=(42,))
|
|
213
|
+
if result.succeeded:
|
|
214
|
+
print(result.final_value)
|
|
215
|
+
else:
|
|
216
|
+
print(result.attempts[-1].failure.traceback)
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
**Custom proposer:**
|
|
220
|
+
```python
|
|
221
|
+
from self_heal.llm import LLMProposer
|
|
222
|
+
|
|
223
|
+
class MyProposer:
|
|
224
|
+
def propose(self, system: str, user: str) -> str:
|
|
225
|
+
# ... your logic ...
|
|
226
|
+
return "def my_tool(x): return x * 2"
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
## Safety
|
|
230
|
+
|
|
231
|
+
`self-heal` executes LLM-generated code via `exec()` in the same process. Same trust boundary as any LLM-in-the-loop system: do not run against untrusted inputs without a sandbox. Sandboxed execution is on the roadmap.
|
|
232
|
+
|
|
233
|
+
## Roadmap
|
|
234
|
+
|
|
235
|
+
- [x] v0.0.1: core repair loop + decorator + Claude backend
|
|
236
|
+
- [x] v0.0.2: OpenAI, Gemini, LiteLLM adapters — works with any LLM
|
|
237
|
+
- [ ] v0.1: user-provided verifiers (beyond exception-catching)
|
|
238
|
+
- [ ] v0.2: telemetry + before/after success metrics
|
|
239
|
+
- [ ] v0.3: async support
|
|
240
|
+
- [ ] v0.4: sandboxed execution
|
|
241
|
+
- [ ] v0.5: repair persistence (learn from past fixes)
|
|
242
|
+
- [ ] v1.0: NeurIPS 2026 paper co-release
|
|
243
|
+
|
|
244
|
+
## Development
|
|
245
|
+
|
|
246
|
+
```bash
|
|
247
|
+
git clone https://github.com/Johin2/self-heal.git
|
|
248
|
+
cd self-heal
|
|
249
|
+
python -m venv .venv
|
|
250
|
+
.venv/Scripts/pip install -e ".[dev]" # Windows
|
|
251
|
+
# .venv/bin/pip install -e ".[dev]" # macOS/Linux
|
|
252
|
+
pytest
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
## License
|
|
256
|
+
|
|
257
|
+
MIT
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# self-heal
|
|
2
|
+
|
|
3
|
+
[](https://github.com/Johin2/self-heal/actions/workflows/ci.yml)
|
|
4
|
+
[](https://pypi.org/project/self-heal-llm/)
|
|
5
|
+
[](https://pypi.org/project/self-heal-llm/)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
> Automatic repair for failing Python code, powered by any LLM.
|
|
9
|
+
|
|
10
|
+
When a function fails, `self-heal` catches the exception, analyzes it with an LLM, proposes a repaired version, and retries. One decorator. Works with Claude, OpenAI, Gemini, and 100+ other providers.
|
|
11
|
+
|
|
12
|
+
```python
|
|
13
|
+
from self_heal import repair
|
|
14
|
+
|
|
15
|
+
@repair(max_attempts=3)
|
|
16
|
+
def extract_price(text: str) -> float:
|
|
17
|
+
# Naive: only handles "$X.YY"
|
|
18
|
+
return float(text.replace("$", ""))
|
|
19
|
+
|
|
20
|
+
print(extract_price("$12.99")) # 12.99
|
|
21
|
+
print(extract_price("₹1,299")) # 1299.0 (repaired)
|
|
22
|
+
print(extract_price("€5,49")) # 5.49 (repaired)
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Install
|
|
26
|
+
|
|
27
|
+
`self-heal` ships with a Protocol and several optional adapters. Install the adapter(s) you want:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install 'self-heal-llm[claude]' # Anthropic Claude (default)
|
|
31
|
+
pip install 'self-heal-llm[openai]' # OpenAI + OpenAI-compatible endpoints
|
|
32
|
+
pip install 'self-heal-llm[gemini]' # Google Gemini
|
|
33
|
+
pip install 'self-heal-llm[litellm]' # 100+ providers via LiteLLM
|
|
34
|
+
pip install 'self-heal-llm[all]' # everything
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
> PyPI distribution name is `self-heal-llm` (the short name `self-heal` was blocked by PyPI's similarity check with an unrelated package). The Python import stays `from self_heal import ...`.
|
|
38
|
+
|
|
39
|
+
## Provider support
|
|
40
|
+
|
|
41
|
+
| Adapter | Covers |
|
|
42
|
+
|---|---|
|
|
43
|
+
| `ClaudeProposer` | Anthropic Claude (native SDK) |
|
|
44
|
+
| `OpenAIProposer` | OpenAI + **any OpenAI-compatible endpoint** (OpenRouter, Together, Groq, Fireworks, Anyscale, Perplexity, xAI, DeepSeek, Azure, Ollama, LM Studio, vLLM, llama.cpp server, ...) |
|
|
45
|
+
| `GeminiProposer` | Google Gemini (native SDK) |
|
|
46
|
+
| `LiteLLMProposer` | 100+ providers via LiteLLM (Bedrock, Vertex, Cohere, Mistral, ...) |
|
|
47
|
+
|
|
48
|
+
## Using different providers
|
|
49
|
+
|
|
50
|
+
**Claude (default):**
|
|
51
|
+
```python
|
|
52
|
+
from self_heal import repair
|
|
53
|
+
|
|
54
|
+
@repair() # uses ClaudeProposer under the hood
|
|
55
|
+
def my_fn(...): ...
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**OpenAI:**
|
|
59
|
+
```python
|
|
60
|
+
from self_heal import repair
|
|
61
|
+
from self_heal.llm import OpenAIProposer
|
|
62
|
+
|
|
63
|
+
@repair(proposer=OpenAIProposer(model="gpt-5"))
|
|
64
|
+
def my_fn(...): ...
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
**Gemini:**
|
|
68
|
+
```python
|
|
69
|
+
from self_heal import repair
|
|
70
|
+
from self_heal.llm import GeminiProposer
|
|
71
|
+
|
|
72
|
+
@repair(proposer=GeminiProposer(model="gemini-2.5-pro"))
|
|
73
|
+
def my_fn(...): ...
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
**Any OpenAI-compatible endpoint (OpenRouter, Groq, Ollama, ...):**
|
|
77
|
+
```python
|
|
78
|
+
from self_heal.llm import OpenAIProposer
|
|
79
|
+
|
|
80
|
+
# OpenRouter — hundreds of models through one key
|
|
81
|
+
proposer = OpenAIProposer(
|
|
82
|
+
model="google/gemini-2.5-pro",
|
|
83
|
+
base_url="https://openrouter.ai/api/v1",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
# Groq — fast inference
|
|
87
|
+
proposer = OpenAIProposer(
|
|
88
|
+
model="llama-3.3-70b-versatile",
|
|
89
|
+
base_url="https://api.groq.com/openai/v1",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
# Local Ollama
|
|
93
|
+
proposer = OpenAIProposer(
|
|
94
|
+
model="llama3.3",
|
|
95
|
+
base_url="http://localhost:11434/v1",
|
|
96
|
+
api_key="ollama",
|
|
97
|
+
)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**LiteLLM catch-all (100+ providers):**
|
|
101
|
+
```python
|
|
102
|
+
from self_heal.llm import LiteLLMProposer
|
|
103
|
+
|
|
104
|
+
proposer = LiteLLMProposer(model="anthropic/claude-sonnet-4-6")
|
|
105
|
+
proposer = LiteLLMProposer(model="bedrock/anthropic.claude-3-5-sonnet")
|
|
106
|
+
proposer = LiteLLMProposer(model="vertex_ai/gemini-2.5-pro")
|
|
107
|
+
proposer = LiteLLMProposer(model="cohere/command-r-plus")
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## Why this exists
|
|
111
|
+
|
|
112
|
+
AI coding agents fail on a lot of real tasks. The industry's current answer is "retry and hope." That's not a strategy.
|
|
113
|
+
|
|
114
|
+
`self-heal` treats repair as a first-class primitive: diagnose the failure, propose a targeted fix, verify, retry. A thin library you can wrap around any Python function or agent tool.
|
|
115
|
+
|
|
116
|
+
Built on ongoing code-repair research (RepairBench, NeurIPS 2026).
|
|
117
|
+
|
|
118
|
+
## How it works
|
|
119
|
+
|
|
120
|
+
1. **Catch** the exception and capture inputs, traceback, and failure type.
|
|
121
|
+
2. **Classify** the failure (validation, exception, assertion).
|
|
122
|
+
3. **Propose** a repaired function via LLM with a failure-aware prompt.
|
|
123
|
+
4. **Recompile** the proposed function into the running process.
|
|
124
|
+
5. **Retry** with the same inputs.
|
|
125
|
+
|
|
126
|
+
All within a single decorator boundary.
|
|
127
|
+
|
|
128
|
+
## API
|
|
129
|
+
|
|
130
|
+
**Decorator:**
|
|
131
|
+
```python
|
|
132
|
+
from self_heal import repair
|
|
133
|
+
|
|
134
|
+
@repair(max_attempts=3, model="claude-sonnet-4-6", verbose=True)
|
|
135
|
+
def my_tool(x): ...
|
|
136
|
+
|
|
137
|
+
my_tool(42)
|
|
138
|
+
my_tool.last_repair # -> RepairResult with full attempt history
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Loop (for advanced use):**
|
|
142
|
+
```python
|
|
143
|
+
from self_heal import RepairLoop
|
|
144
|
+
|
|
145
|
+
loop = RepairLoop(max_attempts=5, verbose=True)
|
|
146
|
+
result = loop.run(my_tool, args=(42,))
|
|
147
|
+
if result.succeeded:
|
|
148
|
+
print(result.final_value)
|
|
149
|
+
else:
|
|
150
|
+
print(result.attempts[-1].failure.traceback)
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
**Custom proposer:**
|
|
154
|
+
```python
|
|
155
|
+
from self_heal.llm import LLMProposer
|
|
156
|
+
|
|
157
|
+
class MyProposer:
|
|
158
|
+
def propose(self, system: str, user: str) -> str:
|
|
159
|
+
# ... your logic ...
|
|
160
|
+
return "def my_tool(x): return x * 2"
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## Safety
|
|
164
|
+
|
|
165
|
+
`self-heal` executes LLM-generated code via `exec()` in the same process. Same trust boundary as any LLM-in-the-loop system: do not run against untrusted inputs without a sandbox. Sandboxed execution is on the roadmap.
|
|
166
|
+
|
|
167
|
+
## Roadmap
|
|
168
|
+
|
|
169
|
+
- [x] v0.0.1: core repair loop + decorator + Claude backend
|
|
170
|
+
- [x] v0.0.2: OpenAI, Gemini, LiteLLM adapters — works with any LLM
|
|
171
|
+
- [ ] v0.1: user-provided verifiers (beyond exception-catching)
|
|
172
|
+
- [ ] v0.2: telemetry + before/after success metrics
|
|
173
|
+
- [ ] v0.3: async support
|
|
174
|
+
- [ ] v0.4: sandboxed execution
|
|
175
|
+
- [ ] v0.5: repair persistence (learn from past fixes)
|
|
176
|
+
- [ ] v1.0: NeurIPS 2026 paper co-release
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
git clone https://github.com/Johin2/self-heal.git
|
|
182
|
+
cd self-heal
|
|
183
|
+
python -m venv .venv
|
|
184
|
+
.venv/Scripts/pip install -e ".[dev]" # Windows
|
|
185
|
+
# .venv/bin/pip install -e ".[dev]" # macOS/Linux
|
|
186
|
+
pytest
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
## License
|
|
190
|
+
|
|
191
|
+
MIT
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""Minimal self-heal example.
|
|
2
|
+
|
|
3
|
+
Requires ANTHROPIC_API_KEY in the environment.
|
|
4
|
+
|
|
5
|
+
Run:
|
|
6
|
+
python examples/basic.py
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
from self_heal import repair
|
|
14
|
+
|
|
15
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@repair(max_attempts=3, verbose=True)
|
|
19
|
+
def divide(a: float, b: float) -> float:
|
|
20
|
+
# Naive: crashes on b == 0.
|
|
21
|
+
return a / b
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
print("divide(10, 2) =", divide(10, 2))
|
|
26
|
+
print()
|
|
27
|
+
print("divide(10, 0) =", divide(10, 0))
|
|
28
|
+
print()
|
|
29
|
+
print("Attempts:", divide.last_repair.total_attempts)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"""A realistic self-heal example: messy real-world input.
|
|
2
|
+
|
|
3
|
+
Requires ANTHROPIC_API_KEY in the environment.
|
|
4
|
+
|
|
5
|
+
Run:
|
|
6
|
+
python examples/extract_price.py
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import logging
|
|
12
|
+
|
|
13
|
+
from self_heal import repair
|
|
14
|
+
|
|
15
|
+
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@repair(max_attempts=3, verbose=True)
|
|
19
|
+
def extract_price(text: str) -> float:
|
|
20
|
+
# Naive: only handles "$X.YY" with no commas.
|
|
21
|
+
return float(text.replace("$", ""))
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
samples = ["$12.99", "₹1,299", "€5,49", "1000.50 USD", " $4,999.00 "]
|
|
26
|
+
for sample in samples:
|
|
27
|
+
try:
|
|
28
|
+
value = extract_price(sample)
|
|
29
|
+
print(f"{sample!r:>18} -> {value}")
|
|
30
|
+
except Exception as e:
|
|
31
|
+
print(f"{sample!r:>18} -> FAILED: {e}")
|