vybthon 0.1.0__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.
@@ -0,0 +1,18 @@
1
+ name: publish
2
+
3
+ # Build and publish to PyPI when you publish a GitHub Release.
4
+ on:
5
+ release:
6
+ types: [published]
7
+
8
+ jobs:
9
+ pypi:
10
+ runs-on: ubuntu-latest
11
+ environment: pypi # must match the Trusted Publisher "Environment name"
12
+ permissions:
13
+ id-token: write # required for PyPI trusted publishing (OIDC)
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: astral-sh/setup-uv@v6
17
+ - run: uv build # writes sdist + wheel to dist/
18
+ - uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,16 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # vibe-synthesized function cache
13
+ .vibe_cache/
14
+
15
+ # Secrets
16
+ .env
@@ -0,0 +1 @@
1
+ 3.13
vybthon-0.1.0/PKG-INFO ADDED
@@ -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,29 @@
1
+ # vybthon
2
+
3
+ Objects whose methods are written by an LLM the moment you call them. Call any
4
+ method; if it doesn't exist, a model writes it from the name + arguments, runs
5
+ it, and caches the source to `./.vibe_cache/` for instant reuse.
6
+
7
+ ```python
8
+ from vibe import Vibe
9
+
10
+ v = Vibe.openrouter("openrouter/meta-llama/llama-3.1-8b-instruct") # set OPENROUTER_API_KEY
11
+ v.reverse_string("hello") # -> "olleh"
12
+ v.nth_prime(10) # cached after first call -> 29
13
+ ```
14
+
15
+ ## Install
16
+
17
+ Needs Python ≥ 3.10 and an LLM provider (hosted, or a local Ollama).
18
+
19
+ ```bash
20
+ pip install "git+https://github.com/bhivam/vybthon" # or: uv add "git+..."
21
+ ```
22
+
23
+ The distribution is `vybthon`; the import is `vibe`. On NixOS, run inside
24
+ `nix develop` (litellm's wheels need `libstdc++`).
25
+
26
+ ## Security
27
+
28
+ Synthesized code runs via `exec`. LLM output is untrusted — treat every
29
+ generated function as arbitrary code. This is a toy, not a sandbox.
@@ -0,0 +1,74 @@
1
+ """Prototype: a contact-list importer that cleans messy exported data.
2
+
3
+ A familiar chore — you exported contacts from somewhere and the rows are a mess:
4
+ inconsistent name casing, five different phone formats, mixed-case emails, the
5
+ odd broken address. This prototype normalizes and validates every row, then
6
+ prints a tidy report plus a breakdown by email provider.
7
+
8
+ The interesting part: none of the cleaning or validation logic is written by
9
+ hand. Each ``v.<something>(...)`` call is a function that doesn't exist yet —
10
+ the model writes it from the name and argument types on first call, it runs, and
11
+ the source is cached to ./.vibe_cache/ for instant reuse afterwards.
12
+
13
+ Run inside `nix develop` (so libstdc++ is on LD_LIBRARY_PATH) with an
14
+ OPENROUTER_API_KEY in your environment:
15
+
16
+ uv run python examples/demo.py
17
+ """
18
+
19
+ from collections import Counter
20
+
21
+ from vibe import Vibe
22
+
23
+ RAW_CONTACTS = [
24
+ {"name": " ada LOVELACE ", "phone": "(415) 555-0132", "email": "Ada@Gmail.com"},
25
+ {"name": "alan turing", "phone": "415.555.0199", "email": "alan.turing@bletchley.uk"},
26
+ {"name": "GRACE hopper", "phone": "+1 415 555 0150", "email": "grace@navy.mil"},
27
+ {"name": "katherine Johnson", "phone": "4155550177", "email": "not-an-email"},
28
+ {"name": "linus torvalds", "phone": "415-555-0164", "email": "Linus@gmail.com"},
29
+ ]
30
+
31
+
32
+ def main() -> None:
33
+ v = Vibe.openrouter(
34
+ model="openrouter/meta-llama/Llama-3.1-8B-Instruct",
35
+ verbose=True,
36
+ )
37
+
38
+ providers: Counter[str] = Counter()
39
+ rows: list[tuple[str, str, str, bool]] = []
40
+
41
+ for raw in RAW_CONTACTS:
42
+ name = v.normalize_full_name(raw["name"])
43
+ phone = v.format_phone_number_e164(raw["phone"], "US")
44
+ email = raw["email"].strip().lower()
45
+ valid = v.is_valid_email_address(email)
46
+
47
+ if valid:
48
+ shown_email = v.mask_email_address(email)
49
+ provider = v.email_provider_from_address(email)
50
+ if provider: # synthesized code may return None on odd input
51
+ providers[provider] += 1
52
+ else:
53
+ shown_email = "<invalid>"
54
+
55
+ rows.append((name, phone, shown_email, valid))
56
+
57
+ # --- cleaned report -------------------------------------------------
58
+ print(f"\n{'NAME':<20}{'PHONE':<18}{'EMAIL':<22}STATUS")
59
+ print("-" * 66)
60
+ for name, phone, email, valid in rows:
61
+ # str(): synthesized functions may return None, which can't be formatted.
62
+ status = "ok" if valid else "REVIEW"
63
+ print(f"{str(name):<20}{str(phone):<18}{str(email):<22}{status}")
64
+
65
+ flagged = sum(1 for *_, valid in rows if not valid)
66
+ print(f"\n{len(rows)} contacts imported, {flagged} flagged for review")
67
+
68
+ print("\ncontacts by email provider:")
69
+ for provider, count in providers.most_common():
70
+ print(f" {provider:<12} {count}")
71
+
72
+
73
+ if __name__ == "__main__":
74
+ main()
@@ -0,0 +1,27 @@
1
+ {
2
+ "nodes": {
3
+ "nixpkgs": {
4
+ "locked": {
5
+ "lastModified": 1780749050,
6
+ "narHash": "sha256-3av0pIjlOWQ6rDbNOmpUSvbNnJkGORQKKjb4LtCZsIY=",
7
+ "owner": "nixos",
8
+ "repo": "nixpkgs",
9
+ "rev": "a799d3e3886da994fa307f817a6bc705ae538eeb",
10
+ "type": "github"
11
+ },
12
+ "original": {
13
+ "owner": "nixos",
14
+ "ref": "nixos-unstable",
15
+ "repo": "nixpkgs",
16
+ "type": "github"
17
+ }
18
+ },
19
+ "root": {
20
+ "inputs": {
21
+ "nixpkgs": "nixpkgs"
22
+ }
23
+ }
24
+ },
25
+ "root": "root",
26
+ "version": 7
27
+ }
@@ -0,0 +1,27 @@
1
+ {
2
+ inputs = {
3
+ nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable";
4
+ };
5
+
6
+ outputs = { self, nixpkgs }:
7
+ let
8
+ supportedSystems = [ "x86_64-linux" "aarch64-linux" "x86_64-darwin" "aarch64-darwin" ];
9
+ forEachSupportedSystem = f: nixpkgs.lib.genAttrs supportedSystems (system: f {
10
+ pkgs = import nixpkgs { inherit system; };
11
+ });
12
+ in
13
+ {
14
+ devShells = forEachSupportedSystem ({ pkgs }: {
15
+ default = pkgs.mkShell {
16
+ packages = with pkgs; [
17
+ python3
18
+ uv
19
+ ];
20
+
21
+ # Precompiled Python wheels (e.g. litellm's `tokenizers`) link
22
+ # against a standard libstdc++; expose it so they load at runtime.
23
+ LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ];
24
+ };
25
+ });
26
+ };
27
+ }
@@ -0,0 +1,32 @@
1
+ [project]
2
+ name = "vybthon"
3
+ version = "0.1.0"
4
+ description = "Objects whose methods are written by an LLM the moment you call them"
5
+ readme = "README.md"
6
+ requires-python = ">=3.10"
7
+ authors = [{ name = "bhivam", email = "shivamkajaria@gmail.com" }]
8
+ keywords = ["llm", "codegen", "metaprogramming", "litellm", "openrouter"]
9
+ classifiers = [
10
+ "Development Status :: 3 - Alpha",
11
+ "Intended Audience :: Developers",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3.10",
14
+ "Programming Language :: Python :: 3.11",
15
+ "Programming Language :: Python :: 3.12",
16
+ "Programming Language :: Python :: 3.13",
17
+ "Topic :: Software Development :: Code Generators",
18
+ ]
19
+ dependencies = [
20
+ "litellm>=1.88.1",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/bhivam/vybthon"
25
+ Repository = "https://github.com/bhivam/vybthon"
26
+
27
+ [build-system]
28
+ requires = ["hatchling"]
29
+ build-backend = "hatchling.build"
30
+
31
+ [tool.hatch.build.targets.wheel]
32
+ packages = ["src/vibe"]
@@ -0,0 +1,7 @@
1
+ {
2
+ "venvPath": ".",
3
+ "venv": ".venv",
4
+ "pythonVersion": "3.13",
5
+ "extraPaths": ["src"],
6
+ "include": ["src", "examples"]
7
+ }
@@ -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"]
@@ -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
@@ -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()