simit 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.
- simit/__init__.py +39 -0
- simit/api.py +258 -0
- simit/backends/__init__.py +112 -0
- simit/backends/bagel.py +359 -0
- simit/backends/base.py +114 -0
- simit/backends/hf.py +336 -0
- simit/backends/imagegen.py +136 -0
- simit/backends/lance.py +338 -0
- simit/backends/mot/engine.py +844 -0
- simit/backends/mot/kernels.py +130 -0
- simit/backends/mot/modeling.py +514 -0
- simit/backends/mot/wan_vae.py +872 -0
- simit/backends/vllm.py +204 -0
- simit/config.py +112 -0
- simit/metrics.py +113 -0
- simit/pipeline/core.py +586 -0
- simit/pipeline/prompts.py +167 -0
- simit/skills/__init__.py +6 -0
- simit/skills/assets/MERMAID_LICENSE +21 -0
- simit/skills/assets/mermaid.min.js +3587 -0
- simit/skills/base.py +172 -0
- simit/skills/builtin.py +614 -0
- simit/skills/helpers.py +197 -0
- simit/skills/pool.py +262 -0
- simit/skills/prompts.py +2374 -0
- simit/skills/renderers.py +1292 -0
- simit/skills/routing.py +89 -0
- simit/skills/sandbox.py +188 -0
- simit/skills/web.py +253 -0
- simit/tune.py +323 -0
- simit/types.py +96 -0
- simit/utils.py +67 -0
- simit-0.1.0.dist-info/METADATA +388 -0
- simit-0.1.0.dist-info/RECORD +38 -0
- simit-0.1.0.dist-info/WHEEL +5 -0
- simit-0.1.0.dist-info/licenses/LICENSE +202 -0
- simit-0.1.0.dist-info/licenses/src/simit/skills/assets/MERMAID_LICENSE +21 -0
- simit-0.1.0.dist-info/top_level.txt +1 -0
simit/skills/base.py
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"""The ``Skill`` interface: how a textual image description becomes an image.
|
|
2
|
+
|
|
3
|
+
A skill turns a description into a compact specification written by the
|
|
4
|
+
model (``prompt`` -> model -> ``parse``), then draws it deterministically
|
|
5
|
+
(``render``). Parser/renderer errors are fed back to the model for repair.
|
|
6
|
+
Subclass ``Skill`` to add a new visual domain::
|
|
7
|
+
|
|
8
|
+
class SheetMusic(simit.Skill):
|
|
9
|
+
name = "sheet_music"
|
|
10
|
+
route_hint = "musical notation: staves, notes, chords"
|
|
11
|
+
examples = [("Draw a C major scale", "NOTES: C4 D4 E4 F4 G4 A4 B4 C5")]
|
|
12
|
+
def parse(self, text): ... # raise SpecError(msg); msg is fed back
|
|
13
|
+
def render(self, spec): ... # -> PIL.Image
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
from __future__ import annotations
|
|
17
|
+
|
|
18
|
+
import re
|
|
19
|
+
from dataclasses import dataclass, field
|
|
20
|
+
from typing import TYPE_CHECKING, Any, Optional, Sequence
|
|
21
|
+
|
|
22
|
+
from PIL import Image
|
|
23
|
+
|
|
24
|
+
from ..utils import clean_response
|
|
25
|
+
|
|
26
|
+
if TYPE_CHECKING:
|
|
27
|
+
from .context import SkillContext
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class SpecError(ValueError):
|
|
31
|
+
"""Raise from ``Skill.parse``/``Skill.render`` when a specification is
|
|
32
|
+
unusable. The message is shown to the model in the repair prompt, so make
|
|
33
|
+
it specific ("no NODE: lines found", "undefined point 'C'")."""
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@dataclass
|
|
37
|
+
class Realization:
|
|
38
|
+
image: Image.Image
|
|
39
|
+
skill: str
|
|
40
|
+
spec_text: str = "" # the model output the image was rendered from
|
|
41
|
+
attempts: int = 1
|
|
42
|
+
extra: dict = field(default_factory=dict)
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# Completion-style few-shot framing ("Request: ... / Specification:"): models
|
|
46
|
+
# follow a custom format far more reliably when the prompt ends where the spec
|
|
47
|
+
# starts (BAGEL: 15/15 parseable vs 8/15 with a trailing instruction).
|
|
48
|
+
_DEFAULT_PROMPT = """You are a precise {title} specification writer. Given a description of \
|
|
49
|
+
{hint}, you output ONLY its specification, in exactly the format shown in the examples below \
|
|
50
|
+
-- nothing else (no explanations, no prose).
|
|
51
|
+
|
|
52
|
+
{examples}{note}Request: {{request}}
|
|
53
|
+
Specification:
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
_REPAIR_NOTE = """A previous specification for the next request could not be used ({reason}):
|
|
57
|
+
{previous}
|
|
58
|
+
|
|
59
|
+
Write a corrected one.
|
|
60
|
+
|
|
61
|
+
"""
|
|
62
|
+
|
|
63
|
+
_CUSTOM_PROMPT_REPAIR = """{prompt}
|
|
64
|
+
|
|
65
|
+
Your previous answer could not be used ({reason}). It was:
|
|
66
|
+
{previous}
|
|
67
|
+
|
|
68
|
+
Output ONLY a corrected specification, nothing else."""
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
class Skill:
|
|
72
|
+
"""Base class for a rendering skill. See the module docstring."""
|
|
73
|
+
|
|
74
|
+
#: Unique identifier; the router answers with ``name.upper()``.
|
|
75
|
+
name: str = ""
|
|
76
|
+
#: Category definition shown to the router ("when should I be used?").
|
|
77
|
+
route_hint: str = ""
|
|
78
|
+
#: Example requests that belong to this skill (router few-shots).
|
|
79
|
+
#: Defaults to the requests in ``examples``.
|
|
80
|
+
route_examples: Sequence[str] = ()
|
|
81
|
+
#: ``(request, spec_text)`` few-shot pairs for the auto-built spec prompt.
|
|
82
|
+
examples: Sequence[tuple[str, str]] = ()
|
|
83
|
+
#: Full spec-prompt template containing ``{request}``; auto-built from
|
|
84
|
+
#: ``examples`` when left as ``None``.
|
|
85
|
+
prompt: Optional[str] = None
|
|
86
|
+
max_new_tokens: int = 512
|
|
87
|
+
#: Decoding temperature of the first attempt and of repair attempts
|
|
88
|
+
#: (greedy repairs on a near-identical context repeat the same mistake).
|
|
89
|
+
first_temperature: float = 0.0
|
|
90
|
+
retry_temperature: float = 0.5
|
|
91
|
+
#: Critic rubric: "strict" (every detail must match, for photos) or
|
|
92
|
+
#: "structured" (right kind of visual, readable, ignore exact values).
|
|
93
|
+
verify_rubric: str = "structured"
|
|
94
|
+
#: Render in a worker process (True) or a thread of the main process.
|
|
95
|
+
#: Process rendering needs the skill to be importable/picklable.
|
|
96
|
+
render_in_subprocess: bool = True
|
|
97
|
+
|
|
98
|
+
# ------------------------------------------------------------------ prompts
|
|
99
|
+
@property
|
|
100
|
+
def label(self) -> str:
|
|
101
|
+
return self.name.upper()
|
|
102
|
+
|
|
103
|
+
def build_prompt(self, request: str) -> str:
|
|
104
|
+
template = self.prompt if self.prompt is not None else self._default_prompt()
|
|
105
|
+
return template.format(request=request)
|
|
106
|
+
|
|
107
|
+
def build_repair_prompt(self, request: str, previous: str, reason: str) -> str:
|
|
108
|
+
if self.prompt is not None:
|
|
109
|
+
return _CUSTOM_PROMPT_REPAIR.format(prompt=self.build_prompt(request), previous=previous, reason=reason)
|
|
110
|
+
note = _REPAIR_NOTE.format(reason=reason, previous=previous.strip())
|
|
111
|
+
return self._default_prompt(note).format(request=request)
|
|
112
|
+
|
|
113
|
+
def _default_prompt(self, note: str = "") -> str:
|
|
114
|
+
blocks = [f"Request: {req}\nSpecification:\n{spec.strip()}\n\n" for req, spec in self.examples]
|
|
115
|
+
title = self.name.replace("_", " ")
|
|
116
|
+
hint = self.route_hint.strip() or f"a {title} image"
|
|
117
|
+
|
|
118
|
+
def esc(text): # user/model text must survive the final .format(request=...)
|
|
119
|
+
return text.replace("{", "{{").replace("}", "}}")
|
|
120
|
+
return _DEFAULT_PROMPT.format(title=title, hint=esc(hint), examples=esc("".join(blocks)), note=esc(note))
|
|
121
|
+
|
|
122
|
+
def get_route_examples(self) -> list[str]:
|
|
123
|
+
return list(self.route_examples) or [req for req, _ in self.examples]
|
|
124
|
+
|
|
125
|
+
# ------------------------------------------------------------- spec -> image
|
|
126
|
+
def shortcut(self, request: str) -> Any:
|
|
127
|
+
"""Return a ready spec for ``request`` to skip the model, or None."""
|
|
128
|
+
return None
|
|
129
|
+
|
|
130
|
+
def clean(self, text: str) -> str:
|
|
131
|
+
return clean_response(text).strip()
|
|
132
|
+
|
|
133
|
+
def parse(self, text: str) -> Any:
|
|
134
|
+
raise NotImplementedError
|
|
135
|
+
|
|
136
|
+
def render(self, spec: Any) -> Image.Image:
|
|
137
|
+
raise NotImplementedError
|
|
138
|
+
|
|
139
|
+
# ------------------------------------------------------------- realization
|
|
140
|
+
async def realize(self, ctx: "SkillContext", request: str, retries: int = 2) -> Optional[Realization]:
|
|
141
|
+
"""prompt -> model -> parse -> render, repairing on errors. Returns
|
|
142
|
+
None when every attempt failed."""
|
|
143
|
+
shortcut = self.shortcut(request)
|
|
144
|
+
if shortcut is not None:
|
|
145
|
+
try:
|
|
146
|
+
image = await ctx.render(self, shortcut)
|
|
147
|
+
return Realization(image, self.name, spec_text=str(shortcut), attempts=0)
|
|
148
|
+
except SpecError:
|
|
149
|
+
pass
|
|
150
|
+
prompt = self.build_prompt(request)
|
|
151
|
+
for attempt in range(retries + 1):
|
|
152
|
+
temperature = self.first_temperature if attempt == 0 else self.retry_temperature
|
|
153
|
+
text = self.clean(await ctx.generate(prompt, max_new_tokens=self.max_new_tokens,
|
|
154
|
+
temperature=temperature))
|
|
155
|
+
try:
|
|
156
|
+
spec = self.parse(text)
|
|
157
|
+
image = await ctx.render(self, spec)
|
|
158
|
+
except SpecError as e:
|
|
159
|
+
ctx.log(f"[{self.name}] attempt {attempt}: {e}")
|
|
160
|
+
prompt = self.build_repair_prompt(request, text, str(e))
|
|
161
|
+
continue
|
|
162
|
+
return Realization(image, self.name, spec_text=text, attempts=attempt + 1)
|
|
163
|
+
return None
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
_FENCE_RE = re.compile(r"```(?:[\w-]+)?\s*\n(.*?)```", re.DOTALL)
|
|
167
|
+
|
|
168
|
+
|
|
169
|
+
def extract_fenced_block(text: str) -> Optional[str]:
|
|
170
|
+
"""Content of the first fenced code block, or None."""
|
|
171
|
+
m = _FENCE_RE.search(text)
|
|
172
|
+
return m.group(1).strip() if m else None
|