Compare commits
8 commits
feature/ko
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 28f5f897d5 | |||
| d74b36e563 | |||
| 197a0a3c22 | |||
| 136ae08fe3 | |||
| ea8e2f1db7 | |||
| 16cbff61a8 | |||
|
|
b037098730 | ||
| d2d57bc431 |
3 changed files with 65 additions and 9 deletions
19
README.md
19
README.md
|
|
@ -17,16 +17,17 @@
|
||||||
|
|
||||||
**Dervish** infers **regular expression grammars** from example sequences using the BEX family of algorithms. Given a set of example sequences (strings over some alphabet), it learns a compact regular expression that captures the general pattern.
|
**Dervish** infers **regular expression grammars** from example sequences using the BEX family of algorithms. Given a set of example sequences (strings over some alphabet), it learns a compact regular expression that captures the general pattern.
|
||||||
|
|
||||||
Every codebase has unwritten conventions — the order tasks appear in Ansible roles, the resources a Helm chart always creates, the steps every CI pipeline runs. Nobody writes these down. They emerge from copying and converging.
|
Every codebase has unwritten conventions like the order tasks appear in Ansible roles, the resources a Helm chart always creates, the steps every CI pipeline runs. Nobody writes these down. They emerge from copying and converging.
|
||||||
|
|
||||||
When an LLM agent needs to follow these conventions, it usually has two bad options:
|
When an LLM agent needs to follow these conventions, it usually has two bad options:
|
||||||
|
|
||||||
1. **Stuff every existing file into context** — 15 Ansible roles = 5,000 tokens. You'll hit the context window by the third example.
|
1. **Stuff every existing file into context** - You'll hit the context window by the third example.
|
||||||
2. **Guess from one or two examples** — the LLM infers a pattern and often gets it wrong.
|
2. **Guess from one or two examples** - the LLM infers a pattern and often gets it wrong.
|
||||||
|
|
||||||
Dervish replaces both with a **one-call MCP tool**: pass your sequences, get back a ~60-token grammar. A rule you can trust, at a fraction of the cost.
|
Dervish replaces both with a **one-call MCP tool**: pass your sequences, get back a ~60-token grammar.
|
||||||
|
By leveraging **Minimum Description Length (MDL) scoring**, Dervish treats the grammar discovery problem as an optimal compression task. the resulting rule is optimized to consume as few tokens as possible without losing the pattern.
|
||||||
|
|
||||||
**Without Dervish:** token cost scales linearly with examples. **With Dervish:** one compact grammar describes them all — a ~60–200 token rule instead of thousands of tokens of raw examples. Try it out and you too will say:
|
**Without Dervish:** token cost scales linearly with examples. **With Dervish:** one compact grammar describes them all In a ~60–200 token rule instead of thousands of tokens of raw examples. Try it out and you too will say:
|
||||||
|
|
||||||
<p align="center"><img src="dervish.gif" alt="Dervish animation" width="65%"></p>
|
<p align="center"><img src="dervish.gif" alt="Dervish animation" width="65%"></p>
|
||||||
|
|
||||||
|
|
@ -59,7 +60,7 @@ The primary interface is a **Model Context Protocol (MCP)** server. Connect any
|
||||||
|
|
||||||
### Agent workflow
|
### Agent workflow
|
||||||
|
|
||||||
An LLM agent uses the MCP to discover an unwritten convention from existing examples — compressing hundreds of files into a single ~60-token rule:
|
An LLM agent uses the MCP to discover an schema from existing examples, thereby compressing hundreds of files into a single ~60-token rule:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
User: Generate a new Ansible role for installing PostgreSQL.
|
User: Generate a new Ansible role for installing PostgreSQL.
|
||||||
|
|
@ -83,12 +84,12 @@ Agent: Let me check what pattern the existing community roles follow.
|
||||||
I'll generate the new role following this structure.
|
I'll generate the new role following this structure.
|
||||||
```
|
```
|
||||||
|
|
||||||
**Without Dervish:** the agent stuffs 15 role files into context (5,000+ tokens per role = beyond any context window), or guesses the pattern from 1–2 examples and often gets it wrong.
|
**Without Dervish:** the agent either has to read all 15 role files (5,000+ tokens per role), or guesses the pattern from 1–2 examples and often gets it wrong.
|
||||||
|
|
||||||
**With Dervish:** one MCP call returns a ~60-token grammar known to match 15/15 existing roles. The agent follows it reliably.
|
**With Dervish:** one MCP call returns a ~60-token grammar known to match 15/15 existing roles. The agent follows it reliably.
|
||||||
|
|
||||||
**Core+outlier mode:** When generating a new role, the agent can call with
|
**Core+outlier mode:** When generating a new file, for example a new Ansible role, the agent can call with
|
||||||
`min_coverage=0.8` to learn the mainstream pattern while seeing which roles
|
`min_coverage=0.8` to learn the mainstream pattern while seeing which files
|
||||||
deviate and why — useful when the user's case resembles an outlier
|
deviate and why — useful when the user's case resembles an outlier
|
||||||
(e.g., a PHP app like phpmyadmin that needs raw `lineinfile`).
|
(e.g., a PHP app like phpmyadmin that needs raw `lineinfile`).
|
||||||
|
|
||||||
|
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 124 KiB After Width: | Height: | Size: 121 KiB |
55
generate_chart.py
Normal file
55
generate_chart.py
Normal file
|
|
@ -0,0 +1,55 @@
|
||||||
|
import matplotlib
|
||||||
|
import matplotlib.font_manager as fm
|
||||||
|
fm._load_fontmanager(try_read_cache=False)
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
import numpy as np
|
||||||
|
from matplotlib import patheffects
|
||||||
|
|
||||||
|
plt.rcParams.update({
|
||||||
|
"font.family": "Comic Neue",
|
||||||
|
"font.size": 14.0,
|
||||||
|
"path.sketch": (1, 100, 2),
|
||||||
|
"path.effects": [patheffects.withStroke(linewidth=4, foreground="w")],
|
||||||
|
"axes.linewidth": 1.5,
|
||||||
|
"lines.linewidth": 2.0,
|
||||||
|
"figure.facecolor": "white",
|
||||||
|
"grid.linewidth": 0.0,
|
||||||
|
"axes.grid": False,
|
||||||
|
"axes.unicode_minus": False,
|
||||||
|
"axes.edgecolor": "black",
|
||||||
|
"xtick.major.size": 8,
|
||||||
|
"xtick.major.width": 3,
|
||||||
|
"ytick.major.size": 8,
|
||||||
|
"ytick.major.width": 3,
|
||||||
|
})
|
||||||
|
|
||||||
|
categories = ["Ansible", "Helm", "Go Lint GHA"]
|
||||||
|
without = [900, 210, 450]
|
||||||
|
with_dervish = [60, 70, 30]
|
||||||
|
compression = [f"{w//d}×" for w, d in zip(without, with_dervish)]
|
||||||
|
|
||||||
|
x = np.arange(len(categories))
|
||||||
|
width = 0.3
|
||||||
|
|
||||||
|
fig, ax = plt.subplots(figsize=(8, 4.5))
|
||||||
|
|
||||||
|
bars1 = ax.bar(x - width/2, without, width, label="Without Dervish", color="#888888", edgecolor="black")
|
||||||
|
bars2 = ax.bar(x + width/2, with_dervish, width, label="With Dervish", color="#853E91", edgecolor="black")
|
||||||
|
|
||||||
|
for bar, val in zip(bars1, without):
|
||||||
|
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 10,
|
||||||
|
str(val), ha="center", va="bottom", fontsize=9)
|
||||||
|
for bar, val, comp in zip(bars2, with_dervish, compression):
|
||||||
|
ax.text(bar.get_x() + bar.get_width()/2, bar.get_height() + 10,
|
||||||
|
f"{val} ({comp})", ha="center", va="bottom", fontsize=9)
|
||||||
|
|
||||||
|
ax.set_ylabel("Tokens")
|
||||||
|
ax.set_title("Token Savings per Dataset")
|
||||||
|
ax.set_xticks(x)
|
||||||
|
ax.set_xticklabels(categories)
|
||||||
|
ax.legend(frameon=True)
|
||||||
|
ax.set_ylim(0, 1100)
|
||||||
|
fig.tight_layout()
|
||||||
|
fig.savefig("chart_token_savings.png", dpi=200)
|
||||||
|
plt.close()
|
||||||
|
print("chart_token_savings.png regenerated")
|
||||||
Loading…
Add table
Reference in a new issue