appbinary-inspector 0.4.0__tar.gz → 0.6.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.
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/PKG-INFO +24 -1
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/README.md +23 -0
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/pyproject.toml +1 -1
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/__init__.py +3 -2
- appbinary_inspector-0.6.0/src/appbinary_inspector/card_api.py +37 -0
- appbinary_inspector-0.6.0/src/appbinary_inspector/chat_server.py +67 -0
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/cli.py +8 -0
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/.github/workflows/publish.yml +0 -0
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/LICENSE +0 -0
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/analyzer.py +0 -0
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/cardstack.py +0 -0
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/extract.py +0 -0
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/formats.py +0 -0
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/tokenizer.py +0 -0
- {appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/torch_builder.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: appbinary-inspector
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.6.0
|
|
4
4
|
Summary: Open-source lazy-card character model plus static binary inspection tools.
|
|
5
5
|
Author: William Laurento
|
|
6
6
|
License: MIT
|
|
@@ -72,6 +72,29 @@ appbin card-train my-card-model training.txt --steps 500
|
|
|
72
72
|
appbin card-generate my-card-model "Hello" --characters 80
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
## Text chat interface
|
|
76
|
+
|
|
77
|
+
Open a local chat box after training a model. It binds only to your computer,
|
|
78
|
+
opens in your browser, and never sends prompts to a remote service.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
appbin chat my-card-model
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
## Python import API
|
|
85
|
+
|
|
86
|
+
Use the model from normal Python code without a terminal command interface:
|
|
87
|
+
|
|
88
|
+
```python
|
|
89
|
+
from appbinary_inspector import appbincardcr
|
|
90
|
+
|
|
91
|
+
mycard = appbincardcr("my-card-model")
|
|
92
|
+
reply = mycard("Explain gravity", output=True)
|
|
93
|
+
|
|
94
|
+
# Or call it in one line:
|
|
95
|
+
reply = appbincardcr(mycard, "Explain gravity", output=True)
|
|
96
|
+
```
|
|
97
|
+
|
|
75
98
|
This reduces active model-card memory. It does not make a dense, unmodified
|
|
76
99
|
70B model fit instantly on a low-VRAM GPU: disk bandwidth and CPU/GPU transfer
|
|
77
100
|
time become the tradeoff, and models need to be trained for sparse routing.
|
|
@@ -57,6 +57,29 @@ appbin card-train my-card-model training.txt --steps 500
|
|
|
57
57
|
appbin card-generate my-card-model "Hello" --characters 80
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
+
## Text chat interface
|
|
61
|
+
|
|
62
|
+
Open a local chat box after training a model. It binds only to your computer,
|
|
63
|
+
opens in your browser, and never sends prompts to a remote service.
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
appbin chat my-card-model
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Python import API
|
|
70
|
+
|
|
71
|
+
Use the model from normal Python code without a terminal command interface:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from appbinary_inspector import appbincardcr
|
|
75
|
+
|
|
76
|
+
mycard = appbincardcr("my-card-model")
|
|
77
|
+
reply = mycard("Explain gravity", output=True)
|
|
78
|
+
|
|
79
|
+
# Or call it in one line:
|
|
80
|
+
reply = appbincardcr(mycard, "Explain gravity", output=True)
|
|
81
|
+
```
|
|
82
|
+
|
|
60
83
|
This reduces active model-card memory. It does not make a dense, unmodified
|
|
61
84
|
70B model fit instantly on a low-VRAM GPU: disk bandwidth and CPU/GPU transfer
|
|
62
85
|
time become the tradeoff, and models need to be trained for sparse routing.
|
|
@@ -4,6 +4,7 @@ from .analyzer import inspect_file
|
|
|
4
4
|
from .extract import extract_binary_parts, extract_embedded
|
|
5
5
|
from .tokenizer import WordTokenizer
|
|
6
6
|
from .cardstack import CardStackConfig, CardStackLM, train_next_character
|
|
7
|
+
from .card_api import Card, appbincardcr
|
|
7
8
|
|
|
8
|
-
__all__ = ["inspect_file", "extract_embedded", "extract_binary_parts", "WordTokenizer", "CardStackConfig", "CardStackLM", "train_next_character"]
|
|
9
|
-
__version__ = "0.
|
|
9
|
+
__all__ = ["inspect_file", "extract_embedded", "extract_binary_parts", "WordTokenizer", "CardStackConfig", "CardStackLM", "train_next_character", "Card", "appbincardcr"]
|
|
10
|
+
__version__ = "0.6.0"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Small Python API for using a disk-paged CardStack model in normal code."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
from .cardstack import CardStackLM
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Card:
|
|
10
|
+
"""A callable local text model.
|
|
11
|
+
|
|
12
|
+
Example:
|
|
13
|
+
mycard = appbincardcr("my-card-model")
|
|
14
|
+
reply = mycard("Explain gravity", output=True)
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
def __init__(self, model: str | Path | CardStackLM) -> None:
|
|
18
|
+
self.model = CardStackLM.load(model) if isinstance(model, (str, Path)) else model
|
|
19
|
+
|
|
20
|
+
def __call__(self, text: str, output: bool = False, characters: int = 180, temperature: float = 0.8) -> str:
|
|
21
|
+
reply = self.model.generate(text, new_characters=characters, temperature=temperature)
|
|
22
|
+
if output:
|
|
23
|
+
print(reply)
|
|
24
|
+
return reply
|
|
25
|
+
|
|
26
|
+
def reply(self, text: str, **options) -> str:
|
|
27
|
+
return self(text, **options)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def appbincardcr(mycard: str | Path | CardStackLM | Card, text: str | None = None, output: bool = False, **options):
|
|
31
|
+
"""Create or call a CardStack text model with one short function.
|
|
32
|
+
|
|
33
|
+
``appbincardcr("my-card-model")`` returns a reusable card.
|
|
34
|
+
``appbincardcr(mycard, "Hello", output=True)`` generates and prints text.
|
|
35
|
+
"""
|
|
36
|
+
card = mycard if isinstance(mycard, Card) else Card(mycard)
|
|
37
|
+
return card if text is None else card(text, output=output, **options)
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Local browser chat UI for a trained CardStack model."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import json
|
|
5
|
+
import threading
|
|
6
|
+
import webbrowser
|
|
7
|
+
from http import HTTPStatus
|
|
8
|
+
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
|
|
11
|
+
from .cardstack import CardStackLM
|
|
12
|
+
|
|
13
|
+
PAGE = """<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>CardStack Chat</title><style>
|
|
14
|
+
:root{--bg:#0a0d13;--panel:#121823;--line:#263142;--text:#f1f5f9;--muted:#9dacbf;--accent:#67e8f9;--user:#193352}*{box-sizing:border-box}body{margin:0;background:var(--bg);color:var(--text);font:16px system-ui,-apple-system,Segoe UI,sans-serif}.shell{max-width:920px;min-height:100vh;margin:auto;padding:28px 18px;display:flex;flex-direction:column;gap:20px}header{display:flex;justify-content:space-between;align-items:flex-start;border-bottom:1px solid var(--line);padding-bottom:18px}h1{font-size:22px;margin:0 0 5px}p{margin:0;color:var(--muted)}.badge{font-size:12px;color:var(--accent);border:1px solid #1d6976;border-radius:999px;padding:6px 9px}.chat{flex:1;display:flex;flex-direction:column;gap:14px}.empty{margin:auto;text-align:center;max-width:470px}.empty h2{font-size:24px;margin-bottom:8px}.message{max-width:84%;padding:13px 15px;border:1px solid var(--line);border-radius:14px;white-space:pre-wrap;line-height:1.45}.assistant{align-self:flex-start;background:var(--panel)}.user{align-self:flex-end;background:var(--user)}form{display:flex;gap:10px;position:sticky;bottom:0;background:var(--bg);padding-top:12px}textarea{resize:none;min-height:54px;max-height:180px;flex:1;border:1px solid var(--line);background:var(--panel);color:var(--text);border-radius:12px;padding:15px;font:inherit}button{border:0;border-radius:12px;padding:0 18px;background:var(--accent);color:#062027;font-weight:700;cursor:pointer}button:disabled{opacity:.55;cursor:wait}.hint{font-size:12px;color:var(--muted);text-align:center}@media(max-width:560px){.shell{padding:18px 12px}.badge{display:none}.message{max-width:94%}button{padding:0 14px}}</style></head><body><main class="shell"><header><div><h1>CardStack Chat</h1><p>Disk-paged character model · cards load only when routed</p></div><span class="badge">LOCAL ONLY</span></header><section class="chat" id="chat"><div class="empty" id="empty"><h2>Start a conversation</h2><p>This model produces one next character at a time. Quality depends on the text used to train its cards.</p></div></section><form id="form"><textarea id="prompt" aria-label="Message" placeholder="Type a message" required></textarea><button id="send" type="submit">Send</button></form><div class="hint">Enter to send · Shift+Enter for a new line</div></main><script>const chat=document.querySelector('#chat'),empty=document.querySelector('#empty'),form=document.querySelector('#form'),prompt=document.querySelector('#prompt'),send=document.querySelector('#send');function add(kind,text){empty?.remove();const el=document.createElement('div');el.className='message '+kind;el.textContent=text;chat.append(el);el.scrollIntoView({block:'end',behavior:'smooth'});return el}form.addEventListener('submit',async e=>{e.preventDefault();const text=prompt.value.trim();if(!text)return;add('user',text);prompt.value='';send.disabled=true;const reply=add('assistant','Thinking…');try{const r=await fetch('/api/generate',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({prompt:text,characters:180})});const body=await r.json();reply.textContent=body.text||body.error||'No response.'}catch(err){reply.textContent='Could not reach the local model: '+err.message}finally{send.disabled=false;prompt.focus()}});prompt.addEventListener('keydown',e=>{if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();form.requestSubmit()}});prompt.focus();</script></body></html>"""
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def serve_chat(model_directory: str | Path, host: str = "127.0.0.1", port: int = 8765, open_browser: bool = True) -> None:
|
|
18
|
+
"""Serve a local-only chat box for a disk-paged CardStack model."""
|
|
19
|
+
model = CardStackLM.load(model_directory)
|
|
20
|
+
model_lock = threading.Lock()
|
|
21
|
+
|
|
22
|
+
class Handler(BaseHTTPRequestHandler):
|
|
23
|
+
def _send(self, status: int, content_type: str, body: str) -> None:
|
|
24
|
+
encoded = body.encode("utf-8")
|
|
25
|
+
self.send_response(status)
|
|
26
|
+
self.send_header("Content-Type", f"{content_type}; charset=utf-8")
|
|
27
|
+
self.send_header("Content-Length", str(len(encoded)))
|
|
28
|
+
self.end_headers()
|
|
29
|
+
self.wfile.write(encoded)
|
|
30
|
+
|
|
31
|
+
def do_GET(self) -> None:
|
|
32
|
+
if self.path != "/":
|
|
33
|
+
self._send(HTTPStatus.NOT_FOUND, "text/plain", "Not found")
|
|
34
|
+
return
|
|
35
|
+
self._send(HTTPStatus.OK, "text/html", PAGE)
|
|
36
|
+
|
|
37
|
+
def do_POST(self) -> None:
|
|
38
|
+
if self.path != "/api/generate":
|
|
39
|
+
self._send(HTTPStatus.NOT_FOUND, "application/json", '{"error":"Not found"}')
|
|
40
|
+
return
|
|
41
|
+
try:
|
|
42
|
+
size = int(self.headers.get("Content-Length", "0"))
|
|
43
|
+
request = json.loads(self.rfile.read(min(size, 20_000)))
|
|
44
|
+
prompt = str(request.get("prompt", "")).strip()
|
|
45
|
+
characters = max(1, min(int(request.get("characters", 180)), 500))
|
|
46
|
+
if not prompt:
|
|
47
|
+
raise ValueError("Write a message first.")
|
|
48
|
+
with model_lock:
|
|
49
|
+
text = model.generate(prompt, new_characters=characters)
|
|
50
|
+
self._send(HTTPStatus.OK, "application/json", json.dumps({"text": text}))
|
|
51
|
+
except Exception as error:
|
|
52
|
+
self._send(HTTPStatus.BAD_REQUEST, "application/json", json.dumps({"error": str(error)}))
|
|
53
|
+
|
|
54
|
+
def log_message(self, *_args) -> None:
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
server = ThreadingHTTPServer((host, port), Handler)
|
|
58
|
+
address = f"http://{host}:{port}"
|
|
59
|
+
if open_browser:
|
|
60
|
+
webbrowser.open(address)
|
|
61
|
+
print(f"CardStack Chat is running at {address}. Press Ctrl+C to stop.")
|
|
62
|
+
try:
|
|
63
|
+
server.serve_forever()
|
|
64
|
+
except KeyboardInterrupt:
|
|
65
|
+
pass
|
|
66
|
+
finally:
|
|
67
|
+
server.server_close()
|
|
@@ -10,6 +10,7 @@ from .extract import extract_binary_parts
|
|
|
10
10
|
from .tokenizer import WordTokenizer
|
|
11
11
|
from .torch_builder import PRESETS
|
|
12
12
|
from .cardstack import CardStackConfig, CardStackLM, train_next_character
|
|
13
|
+
from .chat_server import serve_chat
|
|
13
14
|
|
|
14
15
|
def main() -> None:
|
|
15
16
|
parser = argparse.ArgumentParser(description="Static application-binary inspector. Never executes targets.")
|
|
@@ -44,6 +45,10 @@ def main() -> None:
|
|
|
44
45
|
card_generate.add_argument("model")
|
|
45
46
|
card_generate.add_argument("prompt")
|
|
46
47
|
card_generate.add_argument("--characters", type=int, default=80)
|
|
48
|
+
chat_cmd = commands.add_parser("chat", help="open a local browser chat box for a trained CardStack model")
|
|
49
|
+
chat_cmd.add_argument("model")
|
|
50
|
+
chat_cmd.add_argument("--port", type=int, default=8765)
|
|
51
|
+
chat_cmd.add_argument("--no-browser", action="store_true")
|
|
47
52
|
args = parser.parse_args()
|
|
48
53
|
if args.command == "inspect":
|
|
49
54
|
result = inspect_file(args.file, args.strings)
|
|
@@ -67,6 +72,9 @@ def main() -> None:
|
|
|
67
72
|
result = {"model": args.model, "steps": args.steps, "loss_samples": losses}
|
|
68
73
|
elif args.command == "card-generate":
|
|
69
74
|
result = {"text": CardStackLM.load(args.model).generate(args.prompt, new_characters=args.characters)}
|
|
75
|
+
elif args.command == "chat":
|
|
76
|
+
serve_chat(args.model, port=args.port, open_browser=not args.no_browser)
|
|
77
|
+
return
|
|
70
78
|
else:
|
|
71
79
|
result = PRESETS[args.name]
|
|
72
80
|
print(json.dumps(result, indent=2))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/cardstack.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/tokenizer.py
RENAMED
|
File without changes
|
{appbinary_inspector-0.4.0 → appbinary_inspector-0.6.0}/src/appbinary_inspector/torch_builder.py
RENAMED
|
File without changes
|