penguiflow 1.0.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.

Potentially problematic release.


This version of penguiflow might be problematic. Click here for more details.

@@ -0,0 +1,392 @@
1
+ Metadata-Version: 2.4
2
+ Name: penguiflow
3
+ Version: 1.0.0
4
+ Summary: Async agent orchestration primitives.
5
+ Author: PenguiFlow Team
6
+ License: MIT License
7
+
8
+ Copyright (c) 2025 hurtener
9
+
10
+ Permission is hereby granted, free of charge, to any person obtaining a copy
11
+ of this software and associated documentation files (the "Software"), to deal
12
+ in the Software without restriction, including without limitation the rights
13
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
14
+ copies of the Software, and to permit persons to whom the Software is
15
+ furnished to do so, subject to the following conditions:
16
+
17
+ The above copyright notice and this permission notice shall be included in all
18
+ copies or substantial portions of the Software.
19
+
20
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
25
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
26
+ SOFTWARE.
27
+
28
+ Project-URL: Homepage, https://github.com/penguiflow/penguiflow
29
+ Requires-Python: >=3.12
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ Requires-Dist: pydantic>=2.6
33
+ Provides-Extra: dev
34
+ Requires-Dist: mypy>=1.8; extra == "dev"
35
+ Requires-Dist: pytest>=7.4; extra == "dev"
36
+ Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
37
+ Requires-Dist: ruff>=0.2; extra == "dev"
38
+ Dynamic: license-file
39
+
40
+ # PenguiFlow ๐Ÿงโ„๏ธ
41
+
42
+ <p align="center">
43
+ <img src="asset/Penguiflow.png" alt="PenguiFlow logo" width="220">
44
+ </p>
45
+
46
+ **Async-first orchestration library for multi-agent and data pipelines**
47
+
48
+ PenguiFlow is a **lightweight Python library** to orchestrate agent flows.
49
+ It provides:
50
+
51
+ * **Typed, async message passing** (Pydantic v2)
52
+ * **Concurrent fan-out / fan-in patterns**
53
+ * **Routing & decision points**
54
+ * **Retries, timeouts, backpressure**
55
+ * **Dynamic loops** (controller nodes)
56
+ * **Runtime playbooks** (callable subflows with shared metadata)
57
+
58
+ Built on pure `asyncio` (no threads), PenguiFlow is small, predictable, and repo-agnostic.
59
+ Product repos only define **their models + node functions** โ€” the core stays dependency-light.
60
+
61
+ ---
62
+
63
+ ## โœจ Why PenguiFlow?
64
+
65
+ * **Orchestration is everywhere.** Every Pengui service needs to connect LLMs, retrievers, SQL, or external APIs.
66
+ * **Stop rewriting glue.** This library gives you reusable primitives (nodes, flows, contexts) so you can focus on business logic.
67
+ * **Typed & safe.** Every hop validated with Pydantic.
68
+ * **Lightweight.** Only depends on asyncio + pydantic. No broker, no server, no threads.
69
+
70
+ ---
71
+
72
+ ## ๐Ÿ—๏ธ Core Concepts
73
+
74
+ ### Message
75
+
76
+ Every payload is wrapped in a `Message` with headers and metadata.
77
+
78
+ ```python
79
+ from pydantic import BaseModel
80
+ from penguiflow.types import Message, Headers
81
+
82
+ class QueryIn(BaseModel):
83
+ text: str
84
+
85
+ msg = Message(
86
+ payload=QueryIn(text="unique reach last 30 days"),
87
+ headers=Headers(tenant="acme")
88
+ )
89
+ ```
90
+
91
+ ### Node
92
+
93
+ A node is an async function wrapped with a `Node`.
94
+ It validates inputs/outputs (via `ModelRegistry`) and applies `NodePolicy` (timeout, retries, etc.).
95
+
96
+ ```python
97
+ from penguiflow.node import Node
98
+
99
+ class QueryOut(BaseModel):
100
+ topic: str
101
+
102
+ async def triage(m: QueryIn) -> QueryOut:
103
+ return QueryOut(topic="metrics")
104
+
105
+ triage_node = Node(triage, name="triage")
106
+ ```
107
+
108
+ ### Flow
109
+
110
+ A flow wires nodes together in a directed graph.
111
+ Edges are called **Floe**s, and flows have two invisible contexts:
112
+
113
+ * **OpenSea** ๐ŸŒŠ โ€” ingress (start of the flow)
114
+ * **Rookery** ๐Ÿง โ€” egress (end of the flow)
115
+
116
+ ```python
117
+ from penguiflow.core import create
118
+
119
+ flow = create(
120
+ triage_node.to(packer_node)
121
+ )
122
+ ```
123
+
124
+ ### Running a Flow
125
+
126
+ ```python
127
+ from penguiflow.registry import ModelRegistry
128
+
129
+ registry = ModelRegistry()
130
+ registry.register("triage", QueryIn, QueryOut)
131
+ registry.register("packer", QueryOut, PackOut)
132
+
133
+ flow.run(registry=registry)
134
+
135
+ await flow.emit(msg) # emit into OpenSea
136
+ out = await flow.fetch() # fetch from Rookery
137
+ print(out.payload) # PackOut(...)
138
+ await flow.stop()
139
+ ```
140
+
141
+ ---
142
+
143
+ ## ๐Ÿงญ Design Principles
144
+
145
+ 1. **Async-only (`asyncio`).**
146
+
147
+ * Flows are orchestrators, mostly I/O-bound.
148
+ * Async tasks are cheap, predictable, and cancellable.
149
+ * Heavy CPU work should be offloaded inside a node (process pool, Ray, etc.), not in PenguiFlow itself.
150
+
151
+ 2. **Typed contracts.**
152
+
153
+ * In/out models per node are defined with Pydantic.
154
+ * Validated at runtime via cached `TypeAdapter`s.
155
+
156
+ 3. **Reliability first.**
157
+
158
+ * Timeouts, retries with backoff, backpressure on queues.
159
+ * Nodes run inside error boundaries.
160
+
161
+ 4. **Minimal dependencies.**
162
+
163
+ * Only asyncio + pydantic.
164
+ * No broker, no server. Everything in-process.
165
+
166
+ 5. **Repo-agnostic.**
167
+
168
+ * Product repos declare their models + node funcs, register them, and run.
169
+ * No product-specific code in the library.
170
+
171
+ ---
172
+
173
+ ## ๐Ÿ“ฆ Installation
174
+
175
+ ```bash
176
+ pip install -e ./penguiflow
177
+ ```
178
+
179
+ Requires **Python 3.12+**.
180
+
181
+ ## ๐Ÿงญ Repo Structure
182
+
183
+ penguiflow/
184
+ __init__.py
185
+ core.py # runtime orchestrator, retries, controller helpers, playbooks
186
+ node.py
187
+ types.py
188
+ registry.py
189
+ patterns.py
190
+ middlewares.py
191
+ viz.py
192
+ README.md
193
+ pyproject.toml # build metadata
194
+ tests/ # pytest suite
195
+ examples/ # runnable flows (fan-out, routing, controller, playbooks)
196
+
197
+ ---
198
+
199
+ ## ๐Ÿš€ Quickstart Example
200
+
201
+ ```python
202
+ from pydantic import BaseModel
203
+ from penguiflow import Headers, Message, ModelRegistry, Node, NodePolicy, create
204
+
205
+
206
+ class TriageIn(BaseModel):
207
+ text: str
208
+
209
+
210
+ class TriageOut(BaseModel):
211
+ text: str
212
+ topic: str
213
+
214
+
215
+ class RetrieveOut(BaseModel):
216
+ topic: str
217
+ docs: list[str]
218
+
219
+
220
+ class PackOut(BaseModel):
221
+ prompt: str
222
+
223
+
224
+ async def triage(msg: TriageIn, ctx) -> TriageOut:
225
+ topic = "metrics" if "metric" in msg.text else "general"
226
+ return TriageOut(text=msg.text, topic=topic)
227
+
228
+
229
+ async def retrieve(msg: TriageOut, ctx) -> RetrieveOut:
230
+ docs = [f"doc_{i}_{msg.topic}" for i in range(2)]
231
+ return RetrieveOut(topic=msg.topic, docs=docs)
232
+
233
+
234
+ async def pack(msg: RetrieveOut, ctx) -> PackOut:
235
+ prompt = f"[{msg.topic}] summarize {len(msg.docs)} docs"
236
+ return PackOut(prompt=prompt)
237
+
238
+
239
+ triage_node = Node(triage, name="triage", policy=NodePolicy(validate="both"))
240
+ retrieve_node = Node(retrieve, name="retrieve", policy=NodePolicy(validate="both"))
241
+ pack_node = Node(pack, name="pack", policy=NodePolicy(validate="both"))
242
+
243
+ registry = ModelRegistry()
244
+ registry.register("triage", TriageIn, TriageOut)
245
+ registry.register("retrieve", TriageOut, RetrieveOut)
246
+ registry.register("pack", RetrieveOut, PackOut)
247
+
248
+ flow = create(
249
+ triage_node.to(retrieve_node),
250
+ retrieve_node.to(pack_node),
251
+ )
252
+ flow.run(registry=registry)
253
+
254
+ message = Message(
255
+ payload=TriageIn(text="show marketing metrics"),
256
+ headers=Headers(tenant="acme"),
257
+ )
258
+
259
+ await flow.emit(message)
260
+ out = await flow.fetch()
261
+ print(out.prompt) # PackOut(prompt='[metrics] summarize 2 docs')
262
+
263
+ await flow.stop()
264
+ ```
265
+
266
+ ### Patterns Toolkit
267
+
268
+ PenguiFlow ships a handful of **composable patterns** to keep orchestration code tidy
269
+ without forcing you into a one-size-fits-all DSL. Each helper is opt-in and can be
270
+ stitched directly into a flow adjacency list:
271
+
272
+ - `map_concurrent(items, worker, max_concurrency=8)` โ€” fan a single message out into
273
+ many in-memory tasks (e.g., batch document enrichment) while respecting a semaphore.
274
+ - `predicate_router(name, mapping)` โ€” route messages to successor nodes based on simple
275
+ boolean functions over payload or headers. Perfect for guardrails or conditional
276
+ tool invocation without building a full controller.
277
+ - `union_router(name, discriminated_model)` โ€” accept a Pydantic discriminated union and
278
+ forward each variant to the matching typed successor node. Keeps type-safety even when
279
+ multiple schema branches exist.
280
+ - `join_k(name, k)` โ€” aggregate `k` messages per `trace_id` before resuming downstream
281
+ work. Useful for fan-out/fan-in batching, map-reduce style summarization, or consensus.
282
+
283
+ All helpers are regular `Node` instances under the hood, so they inherit retries,
284
+ timeouts, and validation just like hand-written nodes.
285
+
286
+ ### Dynamic Controller Loops
287
+
288
+ Long-running agents often need to **think, plan, and act over multiple hops**. PenguiFlow
289
+ models this with a controller node that loops on itself:
290
+
291
+ 1. Define a controller `Node` with `allow_cycle=True` and wire `controller.to(controller)`.
292
+ 2. Emit a `Message` whose payload is a `WM` (working memory). PenguiFlow increments the
293
+ `hops` counter automatically and enforces `budget_hops` + `deadline_s` so controllers
294
+ cannot loop forever.
295
+ 3. The controller can attach intermediate `Thought` artifacts or emit `PlanStep`s for
296
+ transparency/debugging. When it is ready to finish, it returns a `FinalAnswer` which
297
+ is immediately forwarded to Rookery.
298
+
299
+ Deadlines and hop budgets turn into automated `FinalAnswer` error messages, making it
300
+ easy to surface guardrails to downstream consumers.
301
+
302
+ ---
303
+
304
+ ### Playbooks & Subflows
305
+
306
+ Sometimes a controller or router needs to execute a **mini flow** โ€” for example,
307
+ retrieval โ†’ rerank โ†’ compress โ€” without polluting the global topology. `call_playbook`
308
+ spawns a brand-new `PenguiFlow` on demand and wires it into the parent message context:
309
+
310
+ - Trace IDs and headers are reused so observability stays intact.
311
+ - The helper respects optional timeouts and always stops the subflow (even on cancel).
312
+ - The first payload emitted to the playbook's Rookery is returned to the caller,
313
+ allowing you to treat subflows as normal async functions.
314
+
315
+ ```python
316
+ from penguiflow import call_playbook
317
+ from penguiflow.types import Message
318
+
319
+ async def controller(msg: Message, ctx) -> Message:
320
+ playbook_result = await call_playbook(build_retrieval_playbook, msg)
321
+ return msg.model_copy(update={"payload": playbook_result})
322
+ ```
323
+
324
+ Playbooks are ideal for deploying frequently reused toolchains while keeping the main
325
+ flow focused on high-level orchestration logic.
326
+
327
+ ---
328
+
329
+ ## ๐Ÿ›ก๏ธ Reliability & Observability
330
+
331
+ * **NodePolicy**: set validation scope plus per-node timeout, retries, and backoff curves.
332
+ * **Structured logs**: enrich every node event with `{ts, trace_id, node_name, event, latency_ms, q_depth_in, attempt}`.
333
+ * **Middleware hooks**: subscribe observers (e.g., MLflow) to the structured event stream.
334
+
335
+ ---
336
+
337
+ ## ๐Ÿ”ฎ Roadmap
338
+
339
+ * **v1 (current)**: safe core runtime, type-safety, retries, timeouts, routing, controller loops, playbooks via examples.
340
+ * **v2 (future)**: streaming support, per-trace cancel, deadlines/budgets, observability hooks, visualizer, testing harness.
341
+
342
+ ---
343
+
344
+ ## ๐Ÿงช Testing
345
+
346
+ ```bash
347
+ pytest -q
348
+ ```
349
+
350
+ * Unit tests cover core runtime, type safety, routing, retries.
351
+ * Example flows under `examples/` are runnable end-to-end.
352
+
353
+ ---
354
+
355
+ ## ๐Ÿง Naming Glossary
356
+
357
+ * **Node**: an async function + metadata wrapper.
358
+ * **Floe**: an edge (queue) between nodes.
359
+ * **Context**: context passed into each node to fetch/emit.
360
+ * **OpenSea** ๐ŸŒŠ: ingress context.
361
+ * **Rookery** ๐Ÿง: egress context.
362
+
363
+ ---
364
+
365
+ ## ๐Ÿ“– Examples
366
+
367
+ * `examples/quickstart/`: hello world pipeline.
368
+ * `examples/routing_predicate/`: branching with predicates.
369
+ * `examples/routing_union/`: discriminated unions with typed branches.
370
+ * `examples/fanout_join/`: split work and join with `join_k`.
371
+ * `examples/map_concurrent/`: bounded fan-out work inside a node.
372
+ * `examples/controller_multihop/`: dynamic multi-hop agent loop.
373
+ * `examples/reliability_middleware/`: retries, timeouts, and middleware hooks.
374
+ * `examples/playbook_retrieval/`: retrieval โ†’ rerank โ†’ compress playbook.
375
+
376
+ ---
377
+
378
+ ## ๐Ÿค Contributing
379
+
380
+ * Keep the library **lightweight and generic**.
381
+ * Product-specific playbooks go into `examples/`, not core.
382
+ * Every new primitive requires:
383
+
384
+ * Unit tests in `tests/`
385
+ * Runnable example in `examples/`
386
+ * Docs update in README
387
+
388
+ ---
389
+
390
+ ## License
391
+
392
+ MIT
@@ -0,0 +1,13 @@
1
+ penguiflow/__init__.py,sha256=wT0yYO6DsV4x1uLwSXbhcXPP3g9YI9x3mcUWxhX5kis,874
2
+ penguiflow/core.py,sha256=ljXFsmVtWj2UTCxWNok-ZRGrPho0wJN3OWP5IgnfhDU,20143
3
+ penguiflow/middlewares.py,sha256=LUlK4FrMScK3oaNSrAYNw3s4KcAZ716DTLAUqvsOkL8,319
4
+ penguiflow/node.py,sha256=0NOs3rU6t1tHNNwwJopqzM2ufGcp82JpzhckynWBRqs,3563
5
+ penguiflow/patterns.py,sha256=Ivuuy0on0OMsdYd5DRFZm1EgujXKPEaIIMH0ZWlJ1s0,4199
6
+ penguiflow/registry.py,sha256=4lrGDMFjM7c8pfZFc_YG0YHg-F80JyF4c-j0UbAf150,1419
7
+ penguiflow/types.py,sha256=QV2JvB_QnohfBATSaviPWm0HSR9B6dTc3UOwFIYyaqg,1154
8
+ penguiflow/viz.py,sha256=_puIEJevq2DrfNuydm3DG1V1o4PgICxd5pA-Es_IyWY,112
9
+ penguiflow-1.0.0.dist-info/licenses/LICENSE,sha256=JSvodvLXxSct_kI9IBsZOBpVKoESQTB_AGbkClwZ7HI,1065
10
+ penguiflow-1.0.0.dist-info/METADATA,sha256=MhOA10s6CxVM1UbbKPMEkA9FKS-DJMLU7MEYG748g1U,12143
11
+ penguiflow-1.0.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
12
+ penguiflow-1.0.0.dist-info/top_level.txt,sha256=F-5jgzPP4Mo_ErgtzGDFJdRT4CIfFjFBnxxcn-RpWBU,11
13
+ penguiflow-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 hurtener
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ penguiflow