Skip to main content

Self‑evolving decorator: fall back to LLM on failure, then persist clean Python for the happy path.

Project description

evolverx

Self‑evolving Python functions. Wrap a function with the @evolving decorator and, on the first failure, evolverx captures the call context, asks an LLM to synthesize a deterministic function body, validates it in a sandbox, and persists the implementation so future calls run as plain Python. If the function fails again later, it can re‑synthesize.

Status: experimental research prototype. Use in non‑critical environments, review generated code, and prefer running in sandboxes first.

➡️ Release Notes / Changelog: See CHANGELOG.md for latest additions and fixes.

Installation

Requires Python 3.10+.

From PyPI (recommended)

pip install evolverx

With optional dev tools (tests, etc.)

pip install evolverx[dev]

From source (editable mode, for contributors)

git clone https://github.com/vorba-vp/evolverx.git
cd evolverx
pip install -e .[dev]

Set your LLM credentials and optional model:

  • OPENAI_API_KEY must be set in your environment.
  • EVOLVERX_OPENAI_MODEL can override the default model name (defaults to "gpt-5" in code; set this to a model your account supports for the Responses API).

Quick start

Basic self‑evolving function:

from evolverx.evolving import evolving, EvolverxConfig

@evolving(EvolverxConfig())
def add(x: float, y: float) -> float:
    """Return sum of two numbers."""
    raise NotImplementedError

print(add(1, 2))  # First call triggers LLM synthesis; future calls use cached code

With imports and automatic re‑synthesis on any error (similar to examples/example_usage.py):

from evolverx.evolving import evolving, EvolverxConfig

@evolving(
    EvolverxConfig(allow_imports=("json", "time", "requests", "re"), auto_resynthesize_on_any_error=True)
)
def fetch_weather(url: str, params: dict) -> dict:
    """Return weather JSON for given url+params."""
    raise NotImplementedError

print(fetch_weather("https://httpbin.org/get", {"city": "Haifa"}))

How it works

  • Decorator @evolving(config) loads a cached implementation if present, else runs your function.
  • On failure (NotImplementedError or any error if enabled), it prompts the LLM with signature, docstring, source, args/kwargs, and the error.
  • The returned function body is normalized, imports are validated against an allowlist, and it runs in a sandbox with a timeout.
  • If execution succeeds, the full function source is written under .evolverx/cache in your project and hot‑swapped into the running process.

Cache and version control

  • Default cache folder: <project-root>/.evolverx/cache/ (resolved from the file that defines your function).
  • Recommended .gitignore entry:
.evolverx/

If you want to commit generated code, set a tracked path: EvolverxConfig(cache_dir="./evolved_functions").

CLI: view diffs and manage cache

The package ships a small CLI evolverx to inspect changes and clean cache.

If installed from PyPI you already have the CLI evolverx available; no extra step is required.

Default cache location: <project-root>/.evolverx/cache unless overridden in EvolverxConfig.

Show diffs

Print a unified diff to the console, or get paths to Markdown/HTML reports.

# Show unified diff in console
evolverx show <module> <func> --show diff --cache-dir ".\.evolverx\cache"

# Show path to Markdown diff
evolverx show <module> <func> --show md --cache-dir ".\.evolverx\cache"

# Show path to HTML side-by-side diff
evolverx show <module> <func> --show html --cache-dir ".\.evolverx\cache"

Notes:

  • When a function evolves while running a script (e.g., python examples/example_usage.py), the module name is usually __main__.
  • Reports visually preserve the original decorator block so it’s clear the decorator remains, though the cached function file is undecorated.
  • You can regenerate the report files at any time:
evolverx show <module> <func> --regen --show html --cache-dir ".\.evolverx\cache"

Discover available diffs

List diff files to see available <module>__<func> pairs:

Get-ChildItem .\.evolverx\cache\diffs -Filter *.diff | Select-Object -ExpandProperty Name

Clean cache

Remove cached functions and reports (all, module, or function scope):

# Clean entire cache directory
evolverx clean --cache-dir ".\.evolverx\cache"

# Clean a module
evolverx clean --module "__main__" --cache-dir ".\.evolverx\cache"

# Clean a single function in a module
evolverx clean --module "__main__" --func "add" --cache-dir ".\.evolverx\cache"

Exit code is 0; the command prints how many files were removed.

Configuration surface

Key options in EvolverxConfig (see src/evolverx/types.py):

  • allow_imports: tuple of module names allowed in generated code (e.g., "json", "re", "requests").
  • auto_resynthesize_on_any_error: if true, any runtime error triggers a new synthesis attempt.
  • timeout_seconds: sandbox execution timeout per attempt.
  • max_attempts: maximum synthesis attempts per call site.
  • cache_dir: override where evolved functions are written.
  • llm_client / llm_factory: provide a custom language model client (see below).

Supplying a custom LLM client

By default evolverx uses OpenAI via OpenAILLMClient (exposed as LLMClient for backward compatibility). You can plug in any client by implementing the simple protocol:

class MyLLM:
    def generate_function_body(self, prompt: str) -> str:
        # Return ONLY the Python function body (no def line)
        return "return 42"  # trivial example

You can inject your client in one of three ways:

  1. Pass directly to the decorator:
from evolverx.evolving import evolving

@evolving(llm=MyLLM())
def compute(x):
    raise NotImplementedError
  1. Put an instance on the config:
from evolverx.evolving import evolving, EvolverxConfig

cfg = EvolverxConfig(llm_client=MyLLM())

@evolving(cfg)
def compute(x):
    raise NotImplementedError
  1. Provide a factory (handy if you want a new fresh client per decorated function):
cfg = EvolverxConfig(llm_factory=lambda: MyLLM())
@evolving(cfg)
def compute(x):
    raise NotImplementedError

Precedence is: explicit llm= argument to @evolving > llm_client on config > llm_factory on config > default OpenAI client.

Your client may raise exceptions; evolverx will count them toward max_attempts just like sandbox or syntax errors.

Minimal example custom client (see examples/custom_llm_example.py):

class EchoLLM:
    def generate_function_body(self, prompt: str) -> str:
        # For demo: always return deterministic implementation
        return "return 'echo'"

Then:

from evolverx.evolving import evolving
@evolving(llm=EchoLLM())
def foo():
    raise NotImplementedError
print(foo())  # -> 'echo'

Safety notes

  • Generated code is executed in a restricted sandbox with an import allowlist and a timeout; still, review outputs before trusting them.
  • Network/domain allowlisting is planned; today imports gate which clients can be used, but you control them via allow_imports.
  • Keep functions deterministic and side‑effect light for best results.

License

Licensed under the Apache License, Version 2.0. You may not use this project except in compliance with the License. A copy is included in the LICENSE file and is also available at:

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

Legal / Generated Code Disclaimer

This library synthesizes function bodies using an LLM. Generated code may be incorrect, insecure, or unsuitable for production without review. You are responsible for:

  • Verifying correctness, safety, and compliance with your policies.
  • Ensuring prompts and arguments do not leak sensitive data.
  • Auditing any generated logic before deployment.

By contributing, you agree that your contributions are licensed under the Apache-2.0 License and include the patent grant defined therein.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

evolverx-0.2.0.tar.gz (22.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

evolverx-0.2.0-py3-none-any.whl (22.3 kB view details)

Uploaded Python 3

File details

Details for the file evolverx-0.2.0.tar.gz.

File metadata

  • Download URL: evolverx-0.2.0.tar.gz
  • Upload date:
  • Size: 22.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for evolverx-0.2.0.tar.gz
Algorithm Hash digest
SHA256 093fda6537acf79864a109560b056deb64abaf5984937556e7c060719fed07e3
MD5 a63cb5da7e221894288e61e66c41f5bf
BLAKE2b-256 61c16447d4476b69c3e7bac4885b4f3cac56f98255a7f6da35b0026690a79f6a

See more details on using hashes here.

File details

Details for the file evolverx-0.2.0-py3-none-any.whl.

File metadata

  • Download URL: evolverx-0.2.0-py3-none-any.whl
  • Upload date:
  • Size: 22.3 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for evolverx-0.2.0-py3-none-any.whl
Algorithm Hash digest
SHA256 5b74399a83654e8c335327cdda6b8c73bdbff3272b44d88920108635c882f9bf
MD5 7beca560ec929a9c07807f80bc3ec3d3
BLAKE2b-256 5dafc6c8d25121f051116d161248c80e7230216ee53e4057b839502ff85145c0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page