vybthon 0.1.0__py3-none-any.whl
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.
- vibe/__init__.py +16 -0
- vibe/codegen.py +181 -0
- vibe/console.py +22 -0
- vibe/core.py +200 -0
- vybthon-0.1.0.dist-info/METADATA +49 -0
- vybthon-0.1.0.dist-info/RECORD +7 -0
- vybthon-0.1.0.dist-info/WHEEL +4 -0
vibe/__init__.py
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""vibe: objects whose methods are written by an LLM the moment you call them
|
|
2
|
+
|
|
3
|
+
>>> from vibe import Vibe
|
|
4
|
+
>>> v = Vibe()
|
|
5
|
+
>>> v.reverse_string("hello")
|
|
6
|
+
'olleh'
|
|
7
|
+
>>> v.nth_prime(10)
|
|
8
|
+
29
|
|
9
|
+
|
|
10
|
+
vibed code is executed with minimal checks. LLM output is untrusted.
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .codegen import VibeError
|
|
14
|
+
from .core import Vibe
|
|
15
|
+
|
|
16
|
+
__all__ = ["Vibe", "VibeError"]
|
vibe/codegen.py
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from typing import Any, Callable
|
|
5
|
+
|
|
6
|
+
_FENCE_RE = re.compile(r"```(?:[a-zA-Z0-9_+-]*)?\s*\n(.*?)```", re.DOTALL)
|
|
7
|
+
|
|
8
|
+
# How much of each argument to show the model: enough to convey shape and
|
|
9
|
+
# element types without flooding the prompt with large inputs.
|
|
10
|
+
_SAMPLE = 3 # elements shown from a collection argument
|
|
11
|
+
_MAX_REPR = 200 # characters shown from a single value's repr
|
|
12
|
+
|
|
13
|
+
SYSTEM_PROMPT = """\
|
|
14
|
+
You are a Python function synthesizer. Given a function name and the exact \
|
|
15
|
+
arguments it will be called with, you write exactly one Python function \
|
|
16
|
+
implementing the behavior implied by the name.
|
|
17
|
+
|
|
18
|
+
Hard rules:
|
|
19
|
+
- Output ONLY the function definition. No prose, no markdown fences, no usage \
|
|
20
|
+
examples, no test calls, no print statements.
|
|
21
|
+
- The function MUST be named exactly `{name}`.
|
|
22
|
+
- The function MUST be callable exactly as the call shown below. Every parameter \
|
|
23
|
+
the call does not supply MUST have a default value, so that call succeeds. Extra \
|
|
24
|
+
optional parameters are fine; an extra *required* parameter is not. Add precise \
|
|
25
|
+
type hints.
|
|
26
|
+
- Add one concise docstring line.
|
|
27
|
+
- Use ONLY the Python standard library. Third-party packages (e.g. \
|
|
28
|
+
`phonenumbers`, `numpy`, `requests`) are NOT installed and importing them fails \
|
|
29
|
+
at runtime — implement the behavior yourself instead. Put any `import` \
|
|
30
|
+
statements INSIDE the function body.
|
|
31
|
+
- Be pure and deterministic: no I/O, no network, no global state, and no \
|
|
32
|
+
randomness unless the name explicitly implies it.
|
|
33
|
+
- Return the result; never just print it.\
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
USER_PROMPT = """\
|
|
37
|
+
Write the function `{name}`.
|
|
38
|
+
|
|
39
|
+
It will be called like:
|
|
40
|
+
{call}
|
|
41
|
+
|
|
42
|
+
Arguments:
|
|
43
|
+
{arguments}
|
|
44
|
+
|
|
45
|
+
Define `{name}` so this exact call works, then infer the intended behavior from \
|
|
46
|
+
the name and the inputs and return the function definition.\
|
|
47
|
+
"""
|
|
48
|
+
|
|
49
|
+
RETRY_PROMPT = """\
|
|
50
|
+
|
|
51
|
+
The previous version of `{name}` failed when called with these arguments:
|
|
52
|
+
{error}
|
|
53
|
+
|
|
54
|
+
Write a corrected version that does not raise this error.{hint}\
|
|
55
|
+
"""
|
|
56
|
+
|
|
57
|
+
# Extra, error-specific guidance appended to the retry prompt.
|
|
58
|
+
IMPORT_HINT = (
|
|
59
|
+
" That module is NOT installed and never will be — do not reach for another "
|
|
60
|
+
"third-party package. Implement this using ONLY the Python standard library."
|
|
61
|
+
)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
class VibeError(RuntimeError):
|
|
65
|
+
"""Raised when synthesis produces something we can't turn into a function.
|
|
66
|
+
|
|
67
|
+
Carries the offending source; ``str(err)`` appends it with line numbers.
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
def __init__(
|
|
71
|
+
self, message: str, *, name: str | None = None, source: str | None = None
|
|
72
|
+
) -> None:
|
|
73
|
+
super().__init__(message)
|
|
74
|
+
self.name = name
|
|
75
|
+
self.source = source
|
|
76
|
+
|
|
77
|
+
def __str__(self) -> str:
|
|
78
|
+
base = super().__str__()
|
|
79
|
+
if not self.source:
|
|
80
|
+
return base
|
|
81
|
+
width = len(str(self.source.count("\n") + 1))
|
|
82
|
+
numbered = "\n".join(
|
|
83
|
+
f" {i:>{width}} | {line}"
|
|
84
|
+
for i, line in enumerate(self.source.splitlines(), 1)
|
|
85
|
+
)
|
|
86
|
+
label = f" for {self.name!r}" if self.name else ""
|
|
87
|
+
return f"{base}\n\n synthesized source{label}:\n{numbered}"
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def strip_fences(text: str) -> str:
|
|
91
|
+
"""Pull code out of a ```fenced``` block if present, else return as-is."""
|
|
92
|
+
match = _FENCE_RE.search(text)
|
|
93
|
+
return (match.group(1) if match else text).strip()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _preview(value: Any) -> str:
|
|
97
|
+
"""A bounded repr of ``value``: collections sampled, long values truncated."""
|
|
98
|
+
if isinstance(value, (list, tuple, set, frozenset)):
|
|
99
|
+
items = list(value)
|
|
100
|
+
shown = ", ".join(repr(x) for x in items[:_SAMPLE])
|
|
101
|
+
more = f", … (+{len(items) - _SAMPLE} more)" if len(items) > _SAMPLE else ""
|
|
102
|
+
brackets = {list: "[]", tuple: "()", set: "{}", frozenset: "{}"}[type(value)]
|
|
103
|
+
return f"{brackets[0]}{shown}{more}{brackets[1]}"
|
|
104
|
+
if isinstance(value, dict):
|
|
105
|
+
items = list(value.items())
|
|
106
|
+
shown = ", ".join(f"{k!r}: {v!r}" for k, v in items[:_SAMPLE])
|
|
107
|
+
more = f", … (+{len(items) - _SAMPLE} more)" if len(items) > _SAMPLE else ""
|
|
108
|
+
return f"{{{shown}{more}}}"
|
|
109
|
+
text = repr(value)
|
|
110
|
+
if len(text) > _MAX_REPR:
|
|
111
|
+
return f"{text[:_MAX_REPR]}… (+{len(text) - _MAX_REPR} chars)"
|
|
112
|
+
return text
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def render_call(name: str, args: tuple[Any, ...], kwargs: dict[str, Any]) -> str:
|
|
116
|
+
"""Render the call expression, e.g. ``greet('Sam', loud=True)`` (values bounded)."""
|
|
117
|
+
parts = [_preview(a) for a in args]
|
|
118
|
+
parts += [f"{k}={_preview(v)}" for k, v in kwargs.items()]
|
|
119
|
+
return f"{name}({', '.join(parts)})"
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def render_arguments(args: tuple[Any, ...], kwargs: dict[str, Any]) -> str:
|
|
123
|
+
"""One line per argument: position/name, type, and a bounded value preview."""
|
|
124
|
+
lines = [
|
|
125
|
+
f" - positional #{i}: type {type(a).__name__}, value {_preview(a)}"
|
|
126
|
+
for i, a in enumerate(args, 1)
|
|
127
|
+
]
|
|
128
|
+
lines += [
|
|
129
|
+
f" - keyword {k!r}: type {type(v).__name__}, value {_preview(v)}"
|
|
130
|
+
for k, v in kwargs.items()
|
|
131
|
+
]
|
|
132
|
+
return "\n".join(lines) if lines else " (none)"
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def build_messages(
|
|
136
|
+
name: str,
|
|
137
|
+
args: tuple[Any, ...],
|
|
138
|
+
kwargs: dict[str, Any],
|
|
139
|
+
error: Exception | None = None,
|
|
140
|
+
) -> list[dict[str, str]]:
|
|
141
|
+
"""Build the chat messages that ask the model to synthesize ``name``.
|
|
142
|
+
|
|
143
|
+
If ``error`` is given (a previous attempt that raised), its type and message
|
|
144
|
+
are appended so the model can correct course on the retry.
|
|
145
|
+
"""
|
|
146
|
+
user = USER_PROMPT.format(
|
|
147
|
+
name=name,
|
|
148
|
+
call=render_call(name, args, kwargs),
|
|
149
|
+
arguments=render_arguments(args, kwargs),
|
|
150
|
+
)
|
|
151
|
+
if error is not None:
|
|
152
|
+
hint = IMPORT_HINT if isinstance(error, ImportError) else ""
|
|
153
|
+
user += RETRY_PROMPT.format(
|
|
154
|
+
name=name, error=f"{type(error).__name__}: {error}", hint=hint
|
|
155
|
+
)
|
|
156
|
+
return [
|
|
157
|
+
{"role": "system", "content": SYSTEM_PROMPT.format(name=name)},
|
|
158
|
+
{"role": "user", "content": user},
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def materialize(source: str, name: str) -> Callable[..., Any]:
|
|
163
|
+
"""exec ``source`` in a fresh namespace and pull out the function ``name``."""
|
|
164
|
+
namespace: dict[str, Any] = {}
|
|
165
|
+
try:
|
|
166
|
+
exec(compile(source, f"<vibe:{name}>", "exec"), namespace)
|
|
167
|
+
except SyntaxError as exc:
|
|
168
|
+
raise VibeError(
|
|
169
|
+
f"synthesized code for {name!r} is not valid Python: {exc}",
|
|
170
|
+
name=name,
|
|
171
|
+
source=source,
|
|
172
|
+
) from exc
|
|
173
|
+
fn = namespace.get(name)
|
|
174
|
+
if not callable(fn):
|
|
175
|
+
raise VibeError(
|
|
176
|
+
f"synthesized code did not define a callable named {name!r}; "
|
|
177
|
+
f"got {type(fn).__name__}",
|
|
178
|
+
name=name,
|
|
179
|
+
source=source,
|
|
180
|
+
)
|
|
181
|
+
return fn
|
vibe/console.py
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Minimal status output for synthesis. Colour is used only on a TTY."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import sys
|
|
6
|
+
|
|
7
|
+
_DIM = "\033[2m"
|
|
8
|
+
_RESET = "\033[0m"
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Console:
|
|
12
|
+
def __init__(self, *, enabled: bool = False) -> None:
|
|
13
|
+
self._on = enabled
|
|
14
|
+
self._color = enabled and sys.stderr.isatty()
|
|
15
|
+
|
|
16
|
+
def status(self, message: str) -> None:
|
|
17
|
+
"""A dim, prefixed status line, e.g. ``vibe synthesizing reverse_string``."""
|
|
18
|
+
if not self._on:
|
|
19
|
+
return
|
|
20
|
+
line = f"vibe {message}"
|
|
21
|
+
sys.stderr.write(f"{_DIM}{line}{_RESET}\n" if self._color else f"{line}\n")
|
|
22
|
+
sys.stderr.flush()
|
vibe/core.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"""The ``Vibe`` object: undefined methods are synthesized by an LLM on call."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any, Callable
|
|
8
|
+
|
|
9
|
+
import litellm
|
|
10
|
+
from litellm import CustomStreamWrapper, completion
|
|
11
|
+
from litellm.types.utils import ModelResponseStream, StreamingChoices
|
|
12
|
+
|
|
13
|
+
from .codegen import build_messages, materialize, strip_fences
|
|
14
|
+
from .console import Console
|
|
15
|
+
|
|
16
|
+
litellm.suppress_debug_info = True # silence litellm's "Provider List" banner
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class Vibe:
|
|
21
|
+
"""An object whose undefined methods are synthesized by an LLM on first call.
|
|
22
|
+
|
|
23
|
+
Call any method name; if it doesn't exist yet, the model writes a function
|
|
24
|
+
that does what the name + arguments imply, it's executed, and the source is
|
|
25
|
+
cached to disk for reuse on later calls and future runs.
|
|
26
|
+
|
|
27
|
+
SECURITY: synthesized code is executed with ``exec``. LLM output is
|
|
28
|
+
untrusted — treat every generated function as arbitrary code. This is a toy,
|
|
29
|
+
not a sandbox; run it only against a local model you trust.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(
|
|
33
|
+
self,
|
|
34
|
+
model: str,
|
|
35
|
+
*,
|
|
36
|
+
api_base: str | None ,
|
|
37
|
+
api_key: str | None = None,
|
|
38
|
+
extra_body: dict[str, Any] | None = None,
|
|
39
|
+
cache_dir: Path | str | None = None,
|
|
40
|
+
verbose: bool = False,
|
|
41
|
+
retries: int = 2,
|
|
42
|
+
caching: bool = True,
|
|
43
|
+
) -> None:
|
|
44
|
+
self._model = model
|
|
45
|
+
self._api_base = api_base
|
|
46
|
+
self._api_key = api_key
|
|
47
|
+
self._extra_body = extra_body
|
|
48
|
+
self._cache_dir = Path(cache_dir) if cache_dir else Path.cwd() / ".vibe_cache"
|
|
49
|
+
self._retries = retries
|
|
50
|
+
self._console = Console(enabled=verbose)
|
|
51
|
+
self._fns: dict[str, Callable[..., Any]] = {}
|
|
52
|
+
self._cache_dir.mkdir(parents=True, exist_ok=True)
|
|
53
|
+
self._caching = caching
|
|
54
|
+
|
|
55
|
+
@classmethod
|
|
56
|
+
def openrouter(
|
|
57
|
+
cls,
|
|
58
|
+
model: str,
|
|
59
|
+
*,
|
|
60
|
+
reasoning: bool = False,
|
|
61
|
+
**kwargs: Any,
|
|
62
|
+
) -> "Vibe":
|
|
63
|
+
OPENROUTER_BASE_URL = os.getenv("OPENROUTER_API_BASE", "https://openrouter.ai/api/v1")
|
|
64
|
+
OPENROUTER_API_KEY = os.environ.get("OPENROUTER_API_KEY")
|
|
65
|
+
|
|
66
|
+
if reasoning:
|
|
67
|
+
extra_body = {**kwargs.pop("extra_body", {}), "reasoning": {"enabled": True}}
|
|
68
|
+
kwargs["extra_body"] = extra_body
|
|
69
|
+
return cls(model, api_base=OPENROUTER_BASE_URL, api_key=OPENROUTER_API_KEY, **kwargs)
|
|
70
|
+
|
|
71
|
+
# -- public surface ----------------------------------------------------
|
|
72
|
+
|
|
73
|
+
def __getattr__(self, name: str) -> Callable[..., Any]:
|
|
74
|
+
# Never intercept dunder / private lookups (copy, repr, IPython probes,
|
|
75
|
+
# and our own ``self._foo`` access during __init__ all rely on this).
|
|
76
|
+
if name.startswith("_"):
|
|
77
|
+
raise AttributeError(name)
|
|
78
|
+
|
|
79
|
+
def caller(*args: Any, **kwargs: Any) -> Any:
|
|
80
|
+
return self._call(name, args, kwargs)
|
|
81
|
+
|
|
82
|
+
caller.__name__ = name
|
|
83
|
+
caller.__qualname__ = f"Vibe.{name}"
|
|
84
|
+
return caller
|
|
85
|
+
|
|
86
|
+
# -- call / retry ------------------------------------------------------
|
|
87
|
+
|
|
88
|
+
def _call(
|
|
89
|
+
self, name: str, args: tuple[Any, ...], kwargs: dict[str, Any]
|
|
90
|
+
) -> Any:
|
|
91
|
+
"""Resolve and invoke ``name``. If synthesis or the call itself fails,
|
|
92
|
+
evict the bad version and re-synthesize with the exception as context,
|
|
93
|
+
up to ``self._retries`` times."""
|
|
94
|
+
error: Exception | None = None
|
|
95
|
+
for attempt in range(1, self._retries + 2): # 1 initial try + N retries
|
|
96
|
+
try:
|
|
97
|
+
fn = self._resolve(name, args, kwargs, error)
|
|
98
|
+
return fn(*args, **kwargs)
|
|
99
|
+
except Exception as exc:
|
|
100
|
+
self._evict(name) # this version is bad — don't keep or reuse it
|
|
101
|
+
error = exc
|
|
102
|
+
self._console.status(f"{name} failed (attempt {attempt}): {exc!r}")
|
|
103
|
+
assert error is not None
|
|
104
|
+
raise error
|
|
105
|
+
|
|
106
|
+
def _evict(self, name: str) -> None:
|
|
107
|
+
"""Drop a synthesized function from both caches so it is regenerated."""
|
|
108
|
+
self._fns.pop(name, None)
|
|
109
|
+
self._cache_path(name).unlink(missing_ok=True)
|
|
110
|
+
|
|
111
|
+
# -- resolution / caching ---------------------------------------------
|
|
112
|
+
|
|
113
|
+
def _resolve(
|
|
114
|
+
self,
|
|
115
|
+
name: str,
|
|
116
|
+
args: tuple[Any, ...],
|
|
117
|
+
kwargs: dict[str, Any],
|
|
118
|
+
error: Exception | None = None,
|
|
119
|
+
) -> Callable[..., Any]:
|
|
120
|
+
"""Return a callable for ``name``: memory cache → disk cache → synthesize.
|
|
121
|
+
|
|
122
|
+
On a retry (``error`` set) the caches are skipped and a fresh version is
|
|
123
|
+
synthesized with the failure as context.
|
|
124
|
+
"""
|
|
125
|
+
if self._caching and error is None:
|
|
126
|
+
cached = self._fns.get(name)
|
|
127
|
+
if cached is not None:
|
|
128
|
+
return cached
|
|
129
|
+
|
|
130
|
+
path = self._cache_path(name)
|
|
131
|
+
if path.exists():
|
|
132
|
+
fn = materialize(path.read_text(), name)
|
|
133
|
+
self._fns[name] = fn
|
|
134
|
+
return fn
|
|
135
|
+
|
|
136
|
+
source = strip_fences(self._generate(name, args, kwargs, error))
|
|
137
|
+
fn = materialize(source, name)
|
|
138
|
+
if self._caching:
|
|
139
|
+
self._cache_path(name).write_text(self._cache_header(name) + source + "\n")
|
|
140
|
+
self._fns[name] = fn
|
|
141
|
+
return fn
|
|
142
|
+
|
|
143
|
+
def _cache_path(self, name: str) -> Path:
|
|
144
|
+
return self._cache_dir / f"{name}.py"
|
|
145
|
+
|
|
146
|
+
def _cache_header(self, name: str) -> str:
|
|
147
|
+
return (
|
|
148
|
+
f"# vibe-generated: {name}\n"
|
|
149
|
+
f"# model: {self._model}\n"
|
|
150
|
+
f"# WARNING: machine-written code, review before trusting.\n\n"
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
# -- generation --------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
def _generate(
|
|
156
|
+
self,
|
|
157
|
+
name: str,
|
|
158
|
+
args: tuple[Any, ...],
|
|
159
|
+
kwargs: dict[str, Any],
|
|
160
|
+
error: Exception | None = None,
|
|
161
|
+
) -> str:
|
|
162
|
+
"""Synthesize the source for ``name`` (one request; ``_call`` handles retries).
|
|
163
|
+
|
|
164
|
+
When ``error`` is set, it is passed to the model as context so the new
|
|
165
|
+
version avoids whatever the previous one got wrong.
|
|
166
|
+
"""
|
|
167
|
+
self._console.status(f"synthesizing {name}" + (" (retry)" if error else ""))
|
|
168
|
+
source = self._stream(build_messages(name, args, kwargs, error))
|
|
169
|
+
self._console.status(f"synthesized {name}")
|
|
170
|
+
return source
|
|
171
|
+
|
|
172
|
+
def _completion_kwargs(self, messages: list[dict[str, str]]) -> dict[str, Any]:
|
|
173
|
+
"""litellm kwargs; api_base/api_key/extra_body sent only when set."""
|
|
174
|
+
kwargs: dict[str, Any] = {
|
|
175
|
+
"model": self._model,
|
|
176
|
+
"messages": messages,
|
|
177
|
+
"stream": True,
|
|
178
|
+
}
|
|
179
|
+
if self._api_base and not self._model.startswith("openrouter/"):
|
|
180
|
+
kwargs["api_base"] = self._api_base
|
|
181
|
+
if self._api_key:
|
|
182
|
+
kwargs["api_key"] = self._api_key
|
|
183
|
+
if self._extra_body:
|
|
184
|
+
kwargs["extra_body"] = self._extra_body
|
|
185
|
+
return kwargs
|
|
186
|
+
|
|
187
|
+
def _stream(self, messages: list[dict[str, str]]) -> str:
|
|
188
|
+
"""Stream a completion and return the concatenated content."""
|
|
189
|
+
resp = completion(**self._completion_kwargs(messages))
|
|
190
|
+
assert isinstance(resp, CustomStreamWrapper)
|
|
191
|
+
|
|
192
|
+
parts: list[str] = []
|
|
193
|
+
for chunk in resp:
|
|
194
|
+
assert isinstance(chunk, ModelResponseStream)
|
|
195
|
+
choice = chunk.choices[0]
|
|
196
|
+
assert isinstance(choice, StreamingChoices)
|
|
197
|
+
text = choice.delta.content
|
|
198
|
+
if text:
|
|
199
|
+
parts.append(text)
|
|
200
|
+
return "".join(parts)
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: vybthon
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Objects whose methods are written by an LLM the moment you call them
|
|
5
|
+
Project-URL: Homepage, https://github.com/bhivam/vybthon
|
|
6
|
+
Project-URL: Repository, https://github.com/bhivam/vybthon
|
|
7
|
+
Author-email: bhivam <shivamkajaria@gmail.com>
|
|
8
|
+
Keywords: codegen,litellm,llm,metaprogramming,openrouter
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
17
|
+
Requires-Python: >=3.10
|
|
18
|
+
Requires-Dist: litellm>=1.88.1
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# vybthon
|
|
22
|
+
|
|
23
|
+
Objects whose methods are written by an LLM the moment you call them. Call any
|
|
24
|
+
method; if it doesn't exist, a model writes it from the name + arguments, runs
|
|
25
|
+
it, and caches the source to `./.vibe_cache/` for instant reuse.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from vibe import Vibe
|
|
29
|
+
|
|
30
|
+
v = Vibe.openrouter("openrouter/meta-llama/llama-3.1-8b-instruct") # set OPENROUTER_API_KEY
|
|
31
|
+
v.reverse_string("hello") # -> "olleh"
|
|
32
|
+
v.nth_prime(10) # cached after first call -> 29
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Install
|
|
36
|
+
|
|
37
|
+
Needs Python ≥ 3.10 and an LLM provider (hosted, or a local Ollama).
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install "git+https://github.com/bhivam/vybthon" # or: uv add "git+..."
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
The distribution is `vybthon`; the import is `vibe`. On NixOS, run inside
|
|
44
|
+
`nix develop` (litellm's wheels need `libstdc++`).
|
|
45
|
+
|
|
46
|
+
## Security
|
|
47
|
+
|
|
48
|
+
Synthesized code runs via `exec`. LLM output is untrusted — treat every
|
|
49
|
+
generated function as arbitrary code. This is a toy, not a sandbox.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
vibe/__init__.py,sha256=-4kZSCGn-xRVdCNuAOe29LokI5rkAfy53uMITFosurQ,388
|
|
2
|
+
vibe/codegen.py,sha256=puXMRe1j_DmQ_GFEZwUqZ7RFqqm8XsCYCGpDwj8pkB0,6595
|
|
3
|
+
vibe/console.py,sha256=UJIkCdJeWssCqz5uwJmTZ0QqZFBWpyGLF74qSgKSAdk,635
|
|
4
|
+
vibe/core.py,sha256=2E3CxeCvVlqoc71EuiBBC0QC6DKrdyrVW36pnT_TDA4,7420
|
|
5
|
+
vybthon-0.1.0.dist-info/METADATA,sha256=ViBg0SPs9v0gzJTGAbmzSlpMekRo4SktRvApVLvgpTY,1768
|
|
6
|
+
vybthon-0.1.0.dist-info/WHEEL,sha256=mffPy8wBnZQn2VnJUU5jE99KsxaSfiyMHV9Yt0aLVxs,87
|
|
7
|
+
vybthon-0.1.0.dist-info/RECORD,,
|