deepparallel 0.3.1__tar.gz → 0.4.1__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.
- {deepparallel-0.3.1 → deepparallel-0.4.1}/PKG-INFO +4 -1
- {deepparallel-0.3.1 → deepparallel-0.4.1}/README.md +1 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/__init__.py +1 -1
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/cli.py +46 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/renderer.py +55 -5
- deepparallel-0.4.1/deepparallel/research/__init__.py +6 -0
- deepparallel-0.4.1/deepparallel/research/conduit.py +156 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel.egg-info/PKG-INFO +4 -1
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel.egg-info/SOURCES.txt +3 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel.egg-info/requires.txt +3 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/pyproject.toml +2 -1
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_renderer.py +32 -10
- deepparallel-0.4.1/tests/test_research.py +27 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/agent.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/backend.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/branding.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/config.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/fusion.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/licensing.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/registry.json +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/supply_chain.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/system_prompt.txt +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/tools/__init__.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/tools/codeast.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/tools/edit.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/tools/files.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/tools/registry.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/tools/sandbox.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/tools/search.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/tools/shell.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/tools/vision.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel/tools/web.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel.egg-info/dependency_links.txt +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel.egg-info/entry_points.txt +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/deepparallel.egg-info/top_level.txt +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/setup.cfg +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_agent.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_backend.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_backend_chat.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_backend_stream.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_branding.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_cli.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_config.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_fusion.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_issuer_signer.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_licensing.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_supply_chain.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_tool_registry.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_tools_codeast.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_tools_edit.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_tools_files.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_tools_sandbox.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_tools_search.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_tools_shell.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_tools_vision.py +0 -0
- {deepparallel-0.3.1 → deepparallel-0.4.1}/tests/test_tools_web.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepparallel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: DeepParallel - a multi-model agentic coding CLI with cross-model Guardian review, served via Crowe Logic.
|
|
5
5
|
Author-email: Michael Crowe <michael@crowelogic.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -19,6 +19,8 @@ Requires-Dist: cryptography>=42.0.0
|
|
|
19
19
|
Provides-Extra: dev
|
|
20
20
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
21
21
|
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
22
|
+
Provides-Extra: research
|
|
23
|
+
Requires-Dist: numpy>=1.24; extra == "research"
|
|
22
24
|
|
|
23
25
|
# DeepParallel
|
|
24
26
|
|
|
@@ -67,6 +69,7 @@ Set the backend env vars (a `.env` file in the working directory is loaded autom
|
|
|
67
69
|
deepparallel run --yes "..." # auto-approve tool actions
|
|
68
70
|
deepparallel review <file|--diff> # cross-model review as a CI gate (Pro)
|
|
69
71
|
deepparallel audit <file> # supply-chain gate: catch hallucinated deps (Pro)
|
|
72
|
+
deepparallel research conduit # latent-relay research demo (needs [research] extra)
|
|
70
73
|
|
|
71
74
|
## Supply-chain gate
|
|
72
75
|
|
|
@@ -45,6 +45,7 @@ Set the backend env vars (a `.env` file in the working directory is loaded autom
|
|
|
45
45
|
deepparallel run --yes "..." # auto-approve tool actions
|
|
46
46
|
deepparallel review <file|--diff> # cross-model review as a CI gate (Pro)
|
|
47
47
|
deepparallel audit <file> # supply-chain gate: catch hallucinated deps (Pro)
|
|
48
|
+
deepparallel research conduit # latent-relay research demo (needs [research] extra)
|
|
48
49
|
|
|
49
50
|
## Supply-chain gate
|
|
50
51
|
|
|
@@ -546,6 +546,52 @@ def audit(ctx: click.Context, path: str) -> None:
|
|
|
546
546
|
sys.exit(0)
|
|
547
547
|
|
|
548
548
|
|
|
549
|
+
@main.group()
|
|
550
|
+
def research() -> None:
|
|
551
|
+
"""Runnable demonstrators of the ideas behind DeepParallel."""
|
|
552
|
+
|
|
553
|
+
|
|
554
|
+
@research.command("conduit")
|
|
555
|
+
def research_conduit() -> None:
|
|
556
|
+
"""Conduit: relay a thought between models as a hidden state vs. as a word.
|
|
557
|
+
|
|
558
|
+
Frozen synthetic models relay a continuous meaning down a chain of agents two
|
|
559
|
+
ways and we measure how much survives each hop. The latent channel holds the
|
|
560
|
+
meaning; the word channel (one emitted token per hop) collapses. The same
|
|
561
|
+
result on real open-weight models: crowelogic.com/research/conduit
|
|
562
|
+
"""
|
|
563
|
+
try:
|
|
564
|
+
from deepparallel.research import conduit as _conduit
|
|
565
|
+
except ImportError:
|
|
566
|
+
branding.error(
|
|
567
|
+
"this demo needs numpy. Install it with: pip install 'deepparallel[research]'"
|
|
568
|
+
)
|
|
569
|
+
sys.exit(3)
|
|
570
|
+
console.print(f"[{branding.DIM}]running the latent relay (frozen synthetic models)...[/]")
|
|
571
|
+
r = _conduit.demonstrate()
|
|
572
|
+
console.print(
|
|
573
|
+
f"\n[bold]Conduit[/] · meaning retained after K relay hops "
|
|
574
|
+
f"[{branding.DIM}](cosine; 1.0 = perfect)[/]"
|
|
575
|
+
)
|
|
576
|
+
console.print(
|
|
577
|
+
f"[{branding.DIM}] meaning = {r['meaning_dim']}-dim continuous "
|
|
578
|
+
f"word channel = 1 token of {r['vocab']}[/]\n"
|
|
579
|
+
)
|
|
580
|
+
console.print(
|
|
581
|
+
f" [{branding.DIM}]{'hops':>5} {'latent relay':>13} {'word relay':>11} {'gain':>6}[/]"
|
|
582
|
+
)
|
|
583
|
+
for K in r["hops"]:
|
|
584
|
+
lat, wrd = r["latent"][K], r["word"][K]
|
|
585
|
+
console.print(
|
|
586
|
+
f" {K:>5} [{branding.GREEN_HEX}]{lat:>13.3f}[/] "
|
|
587
|
+
f"[{branding.DIM}]{wrd:>11.3f}[/] [{branding.AMBER_HEX}]{lat - wrd:>+6.3f}[/]"
|
|
588
|
+
)
|
|
589
|
+
console.print(
|
|
590
|
+
f"\n[{branding.DIM}] the full hidden state relays more meaning than the single word "
|
|
591
|
+
f"the model would have said.[/]"
|
|
592
|
+
)
|
|
593
|
+
|
|
594
|
+
|
|
549
595
|
@main.command(name="tools")
|
|
550
596
|
def tools_cmd() -> None:
|
|
551
597
|
"""List the agent tools available to DeepParallel."""
|
|
@@ -25,6 +25,15 @@ from deepparallel import branding
|
|
|
25
25
|
_REVEAL_SECONDS = 0.04 # per-line delay for the animated intro (tests set 0)
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def _balance_fences(text: str) -> str:
|
|
29
|
+
"""Close a dangling ``` while streaming so a half-arrived code block renders
|
|
30
|
+
as code instead of leaking its fence as literal text. The closing fence is
|
|
31
|
+
cosmetic for the in-progress frame; the final frame has the real one."""
|
|
32
|
+
if text.count("```") % 2 == 1:
|
|
33
|
+
return text + "\n```"
|
|
34
|
+
return text
|
|
35
|
+
|
|
36
|
+
|
|
28
37
|
class Renderer(ABC):
|
|
29
38
|
@abstractmethod
|
|
30
39
|
def welcome(
|
|
@@ -146,10 +155,18 @@ class RichRenderer(Renderer):
|
|
|
146
155
|
self._console.print(branding.build_transcript_markdown(self._console, text))
|
|
147
156
|
|
|
148
157
|
def answer_stream(self, chunks: Iterable[str]) -> str:
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
158
|
+
"""Stream the answer. On a real terminal, render Markdown live in a panel
|
|
159
|
+
that grows as tokens arrive (headings, code, lists format in place); the
|
|
160
|
+
final frame is the same panel `answer()` would print, so there is no
|
|
161
|
+
double render. On a pipe / non-tty, fall back to raw inline streaming."""
|
|
162
|
+
if self._console.is_terminal:
|
|
163
|
+
return self._stream_live_markdown(chunks)
|
|
164
|
+
return self._stream_inline(chunks)
|
|
165
|
+
|
|
166
|
+
def _stream_inline(self, chunks: Iterable[str]) -> str:
|
|
167
|
+
# Raw token streaming for pipes / non-tty: no Live, never ghosts. The
|
|
168
|
+
# marker is printed only on the first VISIBLE character, so empty /
|
|
169
|
+
# whitespace-leading (tool-only) turns render no stray marker.
|
|
153
170
|
parts: list[str] = []
|
|
154
171
|
started = False
|
|
155
172
|
for c in chunks:
|
|
@@ -158,7 +175,7 @@ class RichRenderer(Renderer):
|
|
|
158
175
|
self._console.print(c, end="", soft_wrap=True, highlight=False, markup=False)
|
|
159
176
|
continue
|
|
160
177
|
if not "".join(parts).strip():
|
|
161
|
-
continue
|
|
178
|
+
continue
|
|
162
179
|
started = True
|
|
163
180
|
self._console.print(
|
|
164
181
|
f"[{branding.DP_ACCENT}]{branding.MARK}[/] ", end="", highlight=False
|
|
@@ -168,6 +185,39 @@ class RichRenderer(Renderer):
|
|
|
168
185
|
self._console.print()
|
|
169
186
|
return "".join(parts)
|
|
170
187
|
|
|
188
|
+
def _stream_live_markdown(self, chunks: Iterable[str]) -> str:
|
|
189
|
+
from rich.live import Live
|
|
190
|
+
|
|
191
|
+
parts: list[str] = []
|
|
192
|
+
started = False
|
|
193
|
+
last_draw = 0.0
|
|
194
|
+
live = Live(
|
|
195
|
+
console=self._console,
|
|
196
|
+
auto_refresh=False, # we drive refreshes; deterministic, no bg thread
|
|
197
|
+
vertical_overflow="visible", # let answers taller than the screen scroll
|
|
198
|
+
)
|
|
199
|
+
try:
|
|
200
|
+
for c in chunks:
|
|
201
|
+
parts.append(c)
|
|
202
|
+
if not started:
|
|
203
|
+
if not "".join(parts).strip():
|
|
204
|
+
continue # hold the panel back until real content arrives
|
|
205
|
+
started = True
|
|
206
|
+
live.start()
|
|
207
|
+
now = time.monotonic()
|
|
208
|
+
if now - last_draw >= 0.06: # throttle to ~16 fps
|
|
209
|
+
live.update(self._answer_panel("".join(parts)), refresh=True)
|
|
210
|
+
last_draw = now
|
|
211
|
+
if started: # final frame: the complete, settled answer
|
|
212
|
+
live.update(self._answer_panel("".join(parts)), refresh=True)
|
|
213
|
+
finally:
|
|
214
|
+
if started:
|
|
215
|
+
live.stop()
|
|
216
|
+
return "".join(parts)
|
|
217
|
+
|
|
218
|
+
def _answer_panel(self, text: str):
|
|
219
|
+
return branding.build_transcript_markdown(self._console, _balance_fences(text))
|
|
220
|
+
|
|
171
221
|
def reasoning(self, text: str) -> None:
|
|
172
222
|
self._console.print(branding.build_reasoning_panel(self._console, text))
|
|
173
223
|
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
"""Conduit: latent-state relay between frozen models (runnable demonstrator).
|
|
2
|
+
|
|
3
|
+
The idea behind DeepParallel's multi-model approach, made concrete. When agents
|
|
4
|
+
collaborate by writing words and reading them back, every hand-off squeezes a
|
|
5
|
+
continuous thought through a single emitted token (~log2(vocab) bits). Conduit
|
|
6
|
+
relays the model's hidden state directly through a tiny connector instead.
|
|
7
|
+
|
|
8
|
+
This is a dependency-light demonstration of the mechanism and the information
|
|
9
|
+
loss it avoids: two FROZEN synthetic transformers of different hidden sizes (so
|
|
10
|
+
the connector is genuinely required) relay a continuous meaning vector down a
|
|
11
|
+
chain of agents two ways, and we measure how much meaning survives each hop:
|
|
12
|
+
|
|
13
|
+
latent : h_i -> connector(W) -> next model (continuous)
|
|
14
|
+
word : h_i -> argmax token -> re-embed (one discrete symbol)
|
|
15
|
+
|
|
16
|
+
Needs numpy (`pip install deepparallel[research]`). The full research, including
|
|
17
|
+
the same result on real open-weight models, is at crowelogic.com/research/conduit.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from __future__ import annotations
|
|
21
|
+
|
|
22
|
+
import numpy as np
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def _gelu(x):
|
|
26
|
+
return 0.5 * x * (1.0 + np.tanh(0.7978845608 * (x + 0.044715 * x**3)))
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _ln(x):
|
|
30
|
+
return (x - x.mean(-1, keepdims=True)) / (x.std(-1, keepdims=True) + 1e-5)
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _softmax(x):
|
|
34
|
+
e = np.exp(x - x.max(-1, keepdims=True))
|
|
35
|
+
return e / e.sum(-1, keepdims=True)
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class _FrozenLM:
|
|
39
|
+
"""A tiny frozen decoder-only transformer with its own hidden size + vocab."""
|
|
40
|
+
|
|
41
|
+
def __init__(self, vocab, d, n_layers, seed):
|
|
42
|
+
self.vocab, self.d = vocab, d
|
|
43
|
+
rng = np.random.default_rng(seed)
|
|
44
|
+
s = 1.0 / np.sqrt(d)
|
|
45
|
+
self.E = rng.standard_normal((vocab, d)) * 0.02
|
|
46
|
+
self.U = rng.standard_normal((d, vocab)) * s
|
|
47
|
+
self.layers = [
|
|
48
|
+
{
|
|
49
|
+
"Wq": rng.standard_normal((d, d)) * s,
|
|
50
|
+
"Wk": rng.standard_normal((d, d)) * s,
|
|
51
|
+
"Wv": rng.standard_normal((d, d)) * s,
|
|
52
|
+
"Wo": rng.standard_normal((d, d)) * s,
|
|
53
|
+
"W1": rng.standard_normal((d, 4 * d)) * s,
|
|
54
|
+
"W2": rng.standard_normal((4 * d, d)) * s,
|
|
55
|
+
}
|
|
56
|
+
for _ in range(n_layers)
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
def _forward(self, x):
|
|
60
|
+
T = x.shape[0]
|
|
61
|
+
mask = np.triu(np.full((T, T), -1e9), k=1)
|
|
62
|
+
for p in self.layers:
|
|
63
|
+
h = _ln(x)
|
|
64
|
+
q, k, v = h @ p["Wq"], h @ p["Wk"], h @ p["Wv"]
|
|
65
|
+
att = _softmax(q @ k.T / np.sqrt(self.d) + mask)
|
|
66
|
+
x = x + (att @ v) @ p["Wo"]
|
|
67
|
+
x = x + _gelu(_ln(x) @ p["W1"]) @ p["W2"]
|
|
68
|
+
return x
|
|
69
|
+
|
|
70
|
+
def hidden_at(self, in_embed):
|
|
71
|
+
return self._forward(in_embed.reshape(1, self.d))[-1]
|
|
72
|
+
|
|
73
|
+
def next_token(self, hidden):
|
|
74
|
+
return int(np.argmax(hidden @ self.U))
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _ridge(X, Y, lam=1e-2):
|
|
78
|
+
Xb = np.hstack([X, np.ones((X.shape[0], 1))])
|
|
79
|
+
return np.linalg.solve(Xb.T @ Xb + lam * np.eye(Xb.shape[1]), Xb.T @ Y)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _affine(W, x):
|
|
83
|
+
return np.hstack([x, np.ones((x.shape[0], 1))]) @ W
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class _Conduit:
|
|
87
|
+
def __init__(self, d_meaning, dims, vocab, seed=7):
|
|
88
|
+
self.dm = d_meaning
|
|
89
|
+
rng = np.random.default_rng(seed)
|
|
90
|
+
self.models = [
|
|
91
|
+
_FrozenLM(vocab, dims[0], 2, seed + 1),
|
|
92
|
+
_FrozenLM(vocab, dims[1], 2, seed + 2),
|
|
93
|
+
]
|
|
94
|
+
self.P = [rng.standard_normal((d_meaning, m.d)) * 0.5 for m in self.models]
|
|
95
|
+
self.conn, self.readout = {}, {}
|
|
96
|
+
|
|
97
|
+
def _encode(self, mi, z):
|
|
98
|
+
return self.models[mi].hidden_at(z @ self.P[mi])
|
|
99
|
+
|
|
100
|
+
def _relay(self, z, K, path):
|
|
101
|
+
mi = 0
|
|
102
|
+
h = self._encode(mi, z)
|
|
103
|
+
for hop in range(1, K + 1):
|
|
104
|
+
mj = hop % len(self.models)
|
|
105
|
+
if path == "latent":
|
|
106
|
+
in_embed = _affine(self.conn[(mi, mj)], h.reshape(1, -1))[0]
|
|
107
|
+
else:
|
|
108
|
+
tok = self.models[mi].next_token(h)
|
|
109
|
+
in_embed = self.models[mj].E[tok]
|
|
110
|
+
h = self.models[mj].hidden_at(in_embed)
|
|
111
|
+
mi = mj
|
|
112
|
+
return h, mi
|
|
113
|
+
|
|
114
|
+
def fit(self, n_train, fit_hops, seed=0):
|
|
115
|
+
rng = np.random.default_rng(seed)
|
|
116
|
+
Z = rng.standard_normal((n_train, self.dm))
|
|
117
|
+
n = len(self.models)
|
|
118
|
+
for i in range(n):
|
|
119
|
+
Hi = np.array([self._encode(i, z) for z in Z])
|
|
120
|
+
for j in range(n):
|
|
121
|
+
self.conn[(i, j)] = _ridge(Hi, Z @ self.P[j])
|
|
122
|
+
for path in ("latent", "word"):
|
|
123
|
+
finals = {0: [], 1: []}
|
|
124
|
+
targets = {0: [], 1: []}
|
|
125
|
+
for K in fit_hops:
|
|
126
|
+
for z in Z:
|
|
127
|
+
h, m = self._relay(z, K, path)
|
|
128
|
+
finals[m].append(h)
|
|
129
|
+
targets[m].append(z)
|
|
130
|
+
for m in finals:
|
|
131
|
+
if finals[m]:
|
|
132
|
+
self.readout[(path, m)] = _ridge(np.array(finals[m]), np.array(targets[m]))
|
|
133
|
+
|
|
134
|
+
def recover(self, z, K, path):
|
|
135
|
+
h, m = self._relay(z, K, path)
|
|
136
|
+
return _affine(self.readout[(path, m)], h.reshape(1, -1))[0]
|
|
137
|
+
|
|
138
|
+
|
|
139
|
+
def _cos(a, b):
|
|
140
|
+
return float(a @ b / (np.linalg.norm(a) * np.linalg.norm(b) + 1e-9))
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def demonstrate(hops=(1, 2, 4, 6, 8)) -> dict:
|
|
144
|
+
"""Run the relay both ways and return meaning retained per hop count."""
|
|
145
|
+
d_meaning, vocab = 16, 256
|
|
146
|
+
link = _Conduit(d_meaning, dims=[32, 48], vocab=vocab)
|
|
147
|
+
link.fit(n_train=800, fit_hops=hops)
|
|
148
|
+
rng = np.random.default_rng(123)
|
|
149
|
+
test = rng.standard_normal((300, d_meaning))
|
|
150
|
+
out = {"latent": {}, "word": {}, "meaning_dim": d_meaning, "vocab": vocab, "hops": list(hops)}
|
|
151
|
+
for path in ("latent", "word"):
|
|
152
|
+
for K in hops:
|
|
153
|
+
out[path][K] = round(
|
|
154
|
+
float(np.mean([_cos(z, link.recover(z, K, path)) for z in test])), 3
|
|
155
|
+
)
|
|
156
|
+
return out
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: deepparallel
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.4.1
|
|
4
4
|
Summary: DeepParallel - a multi-model agentic coding CLI with cross-model Guardian review, served via Crowe Logic.
|
|
5
5
|
Author-email: Michael Crowe <michael@crowelogic.com>
|
|
6
6
|
License: Apache-2.0
|
|
@@ -19,6 +19,8 @@ Requires-Dist: cryptography>=42.0.0
|
|
|
19
19
|
Provides-Extra: dev
|
|
20
20
|
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
21
21
|
Requires-Dist: ruff>=0.6.0; extra == "dev"
|
|
22
|
+
Provides-Extra: research
|
|
23
|
+
Requires-Dist: numpy>=1.24; extra == "research"
|
|
22
24
|
|
|
23
25
|
# DeepParallel
|
|
24
26
|
|
|
@@ -67,6 +69,7 @@ Set the backend env vars (a `.env` file in the working directory is loaded autom
|
|
|
67
69
|
deepparallel run --yes "..." # auto-approve tool actions
|
|
68
70
|
deepparallel review <file|--diff> # cross-model review as a CI gate (Pro)
|
|
69
71
|
deepparallel audit <file> # supply-chain gate: catch hallucinated deps (Pro)
|
|
72
|
+
deepparallel research conduit # latent-relay research demo (needs [research] extra)
|
|
70
73
|
|
|
71
74
|
## Supply-chain gate
|
|
72
75
|
|
|
@@ -18,6 +18,8 @@ deepparallel.egg-info/dependency_links.txt
|
|
|
18
18
|
deepparallel.egg-info/entry_points.txt
|
|
19
19
|
deepparallel.egg-info/requires.txt
|
|
20
20
|
deepparallel.egg-info/top_level.txt
|
|
21
|
+
deepparallel/research/__init__.py
|
|
22
|
+
deepparallel/research/conduit.py
|
|
21
23
|
deepparallel/tools/__init__.py
|
|
22
24
|
deepparallel/tools/codeast.py
|
|
23
25
|
deepparallel/tools/edit.py
|
|
@@ -39,6 +41,7 @@ tests/test_fusion.py
|
|
|
39
41
|
tests/test_issuer_signer.py
|
|
40
42
|
tests/test_licensing.py
|
|
41
43
|
tests/test_renderer.py
|
|
44
|
+
tests/test_research.py
|
|
42
45
|
tests/test_supply_chain.py
|
|
43
46
|
tests/test_tool_registry.py
|
|
44
47
|
tests/test_tools_codeast.py
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "deepparallel"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.4.1"
|
|
8
8
|
description = "DeepParallel - a multi-model agentic coding CLI with cross-model Guardian review, served via Crowe Logic."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "Apache-2.0" }
|
|
@@ -26,6 +26,7 @@ dependencies = [
|
|
|
26
26
|
|
|
27
27
|
[project.optional-dependencies]
|
|
28
28
|
dev = ["pytest>=8.0.0", "ruff>=0.6.0"]
|
|
29
|
+
research = ["numpy>=1.24"]
|
|
29
30
|
|
|
30
31
|
[project.urls]
|
|
31
32
|
Homepage = "https://crowelogic.com"
|
|
@@ -123,14 +123,27 @@ def test_rich_confirm_shows_detail():
|
|
|
123
123
|
assert "added line" in out
|
|
124
124
|
|
|
125
125
|
|
|
126
|
-
def
|
|
126
|
+
def test_rich_answer_stream_returns_text_and_renders_live_markdown():
|
|
127
127
|
buf = io.StringIO()
|
|
128
128
|
con = Console(no_color=True, width=80, file=buf, force_terminal=True, highlight=False)
|
|
129
129
|
r = RichRenderer(console=con)
|
|
130
130
|
full = r.answer_stream(iter(["Hel", "lo ", "world"]))
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
assert
|
|
131
|
+
out = buf.getvalue()
|
|
132
|
+
assert full == "Hello world" # full text preserved for history
|
|
133
|
+
assert "Hello world" in out # rendered in the live answer panel
|
|
134
|
+
assert "◆" in out and "answer" in out # panel marker + title
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def test_rich_answer_stream_renders_markdown_formatting():
|
|
138
|
+
buf = io.StringIO()
|
|
139
|
+
con = Console(no_color=True, width=80, file=buf, force_terminal=True, highlight=False)
|
|
140
|
+
r = RichRenderer(console=con)
|
|
141
|
+
full = r.answer_stream(iter(["# Big\n\n", "body **bold**"]))
|
|
142
|
+
out = buf.getvalue()
|
|
143
|
+
assert full == "# Big\n\nbody **bold**"
|
|
144
|
+
# markdown is rendered, not shown raw: the heading hash is consumed
|
|
145
|
+
assert "Big" in out and "body" in out
|
|
146
|
+
assert "# Big" not in out
|
|
134
147
|
|
|
135
148
|
|
|
136
149
|
def test_rich_answer_stream_empty_prints_nothing():
|
|
@@ -139,20 +152,29 @@ def test_rich_answer_stream_empty_prints_nothing():
|
|
|
139
152
|
r = RichRenderer(console=con)
|
|
140
153
|
full = r.answer_stream(iter([]))
|
|
141
154
|
assert full == ""
|
|
142
|
-
assert buf.getvalue() == "" # tool-only turns
|
|
155
|
+
assert buf.getvalue() == "" # tool-only turns never start the live panel
|
|
143
156
|
|
|
144
157
|
|
|
145
|
-
def
|
|
158
|
+
def test_rich_answer_stream_whitespace_leading_holds_panel():
|
|
146
159
|
buf = io.StringIO()
|
|
147
160
|
con = Console(no_color=True, width=80, file=buf, force_terminal=True, highlight=False)
|
|
148
161
|
r = RichRenderer(console=con)
|
|
149
162
|
full = r.answer_stream(iter(["\n", " ", "\n", "Hello"]))
|
|
150
163
|
out = buf.getvalue()
|
|
151
164
|
assert full == "\n \nHello" # full text preserved for history
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
165
|
+
assert "Hello" in out and "◆" in out
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def test_rich_answer_stream_inline_when_not_a_terminal():
|
|
169
|
+
# pipes / CI: raw inline streaming, no Live panel (no border characters)
|
|
170
|
+
buf = io.StringIO()
|
|
171
|
+
con = Console(no_color=True, width=80, file=buf, highlight=False) # not a terminal
|
|
172
|
+
r = RichRenderer(console=con)
|
|
173
|
+
full = r.answer_stream(iter(["Hel", "lo"]))
|
|
174
|
+
out = buf.getvalue()
|
|
175
|
+
assert full == "Hello"
|
|
176
|
+
assert "Hello" in out
|
|
177
|
+
assert "─" not in out and "answer" not in out # no panel chrome
|
|
156
178
|
|
|
157
179
|
|
|
158
180
|
# ---------------------------------------------------------------- FakeRenderer
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from click.testing import CliRunner
|
|
3
|
+
|
|
4
|
+
from deepparallel.cli import main
|
|
5
|
+
|
|
6
|
+
np = pytest.importorskip("numpy") # research demos are an optional extra
|
|
7
|
+
|
|
8
|
+
from deepparallel.research import conduit # noqa: E402
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def test_demonstrate_latent_beats_word_at_every_hop():
|
|
12
|
+
r = conduit.demonstrate(hops=(1, 4, 8))
|
|
13
|
+
for k in (1, 4, 8):
|
|
14
|
+
assert r["latent"][k] > r["word"][k], f"latent should beat word at hop {k}"
|
|
15
|
+
assert r["latent"][1] > 0.7 # strong signal retained at one hop
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_demonstrate_word_channel_does_not_improve_with_depth():
|
|
19
|
+
r = conduit.demonstrate(hops=(1, 6))
|
|
20
|
+
assert r["word"][6] <= r["word"][1] + 0.05 # lossy re-serialization, no gain
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def test_research_conduit_command_runs_and_prints_table():
|
|
24
|
+
result = CliRunner().invoke(main, ["research", "conduit"])
|
|
25
|
+
assert result.exit_code == 0
|
|
26
|
+
assert "Conduit" in result.output
|
|
27
|
+
assert "latent relay" in result.output
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|