penguiflow 2.0.0__py3-none-any.whl → 2.2.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.
- penguiflow/__init__.py +42 -2
- penguiflow/admin.py +174 -0
- penguiflow/bus.py +30 -0
- penguiflow/catalog.py +146 -0
- penguiflow/core.py +261 -13
- penguiflow/debug.py +30 -0
- penguiflow/metrics.py +9 -0
- penguiflow/middlewares.py +72 -1
- penguiflow/registry.py +21 -0
- penguiflow/remote.py +486 -0
- penguiflow/state.py +64 -0
- penguiflow/testkit.py +107 -2
- {penguiflow-2.0.0.dist-info → penguiflow-2.2.0.dist-info}/METADATA +173 -4
- penguiflow-2.2.0.dist-info/RECORD +27 -0
- penguiflow-2.2.0.dist-info/entry_points.txt +2 -0
- penguiflow-2.2.0.dist-info/top_level.txt +2 -0
- penguiflow_a2a/__init__.py +19 -0
- penguiflow_a2a/server.py +695 -0
- penguiflow-2.0.0.dist-info/RECORD +0 -18
- penguiflow-2.0.0.dist-info/top_level.txt +0 -1
- {penguiflow-2.0.0.dist-info → penguiflow-2.2.0.dist-info}/WHEEL +0 -0
- {penguiflow-2.0.0.dist-info → penguiflow-2.2.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: penguiflow
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.2.0
|
|
4
4
|
Summary: Async agent orchestration primitives.
|
|
5
5
|
Author: PenguiFlow Team
|
|
6
6
|
License: MIT License
|
|
@@ -36,7 +36,14 @@ Requires-Dist: pytest>=7.4; extra == "dev"
|
|
|
36
36
|
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
37
37
|
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
38
38
|
Requires-Dist: coverage[toml]>=7.0; extra == "dev"
|
|
39
|
+
Requires-Dist: hypothesis>=6.103; extra == "dev"
|
|
39
40
|
Requires-Dist: ruff>=0.2; extra == "dev"
|
|
41
|
+
Requires-Dist: fastapi>=0.118; extra == "dev"
|
|
42
|
+
Requires-Dist: httpx>=0.27; extra == "dev"
|
|
43
|
+
Provides-Extra: a2a-server
|
|
44
|
+
Requires-Dist: fastapi>=0.118; extra == "a2a-server"
|
|
45
|
+
Provides-Extra: planner
|
|
46
|
+
Requires-Dist: litellm>=1.77.3; extra == "planner"
|
|
40
47
|
Dynamic: license-file
|
|
41
48
|
|
|
42
49
|
# PenguiFlow 🐧❄️
|
|
@@ -52,6 +59,9 @@ Dynamic: license-file
|
|
|
52
59
|
<a href="https://github.com/penguiflow/penguiflow">
|
|
53
60
|
<img src="https://img.shields.io/badge/coverage-85%25-brightgreen" alt="Coverage">
|
|
54
61
|
</a>
|
|
62
|
+
<a href="https://nightly.link/penguiflow/penguiflow/workflows/benchmarks/main/benchmarks.json.zip">
|
|
63
|
+
<img src="https://img.shields.io/badge/benchmarks-latest-orange" alt="Benchmarks">
|
|
64
|
+
</a>
|
|
55
65
|
<a href="https://pypi.org/project/penguiflow/">
|
|
56
66
|
<img src="https://img.shields.io/pypi/v/penguiflow.svg" alt="PyPI version">
|
|
57
67
|
</a>
|
|
@@ -77,10 +87,39 @@ It provides:
|
|
|
77
87
|
* **Observability hooks** (`FlowEvent` callbacks for logging, MLflow, or custom metrics sinks)
|
|
78
88
|
* **Policy-driven routing** (optional policies steer routers without breaking existing flows)
|
|
79
89
|
* **Traceable exceptions** (`FlowError` captures node/trace metadata and optionally emits to Rookery)
|
|
90
|
+
* **Distribution hooks (opt-in)** — plug a `StateStore` to persist trace history and a
|
|
91
|
+
`MessageBus` to publish floe traffic for remote workers without changing existing flows.
|
|
92
|
+
* **Remote calls (opt-in)** — `RemoteNode` bridges the runtime to external agents through a
|
|
93
|
+
pluggable `RemoteTransport` interface (A2A-ready) while propagating streaming chunks and
|
|
94
|
+
cancellation.
|
|
95
|
+
* **A2A server adapter (opt-in)** — wrap a PenguiFlow graph in a FastAPI surface using
|
|
96
|
+
`penguiflow_a2a.A2AServerAdapter` so other agents can call `message/send`,
|
|
97
|
+
`message/stream`, and `tasks/cancel` while reusing the runtime's backpressure and
|
|
98
|
+
cancellation semantics.
|
|
99
|
+
* **Observability & ops polish** — remote calls emit structured metrics (latency, payload
|
|
100
|
+
sizes, cancel reasons) and the `penguiflow-admin` CLI replays trace history from any
|
|
101
|
+
configured `StateStore` for debugging.
|
|
80
102
|
|
|
81
103
|
Built on pure `asyncio` (no threads), PenguiFlow is small, predictable, and repo-agnostic.
|
|
82
104
|
Product repos only define **their models + node functions** — the core stays dependency-light.
|
|
83
105
|
|
|
106
|
+
## Gold Standard Scorecard
|
|
107
|
+
|
|
108
|
+
| Area | Metric | Target | Current |
|
|
109
|
+
| --- | --- | --- | --- |
|
|
110
|
+
| Hop overhead | µs per hop | ≤ 500 | 398 |
|
|
111
|
+
| Streaming order | gaps/dupes | 0 | 0 |
|
|
112
|
+
| Cancel leakage | orphan tasks | 0 | 0 |
|
|
113
|
+
| Coverage | lines | ≥85% | 87% |
|
|
114
|
+
| Deps | count | ≤2 | 2 |
|
|
115
|
+
| Import time | ms | ≤220 | 203 |
|
|
116
|
+
|
|
117
|
+
## 📑 Core Behavior Spec
|
|
118
|
+
|
|
119
|
+
* [Core Behavior Spec](docs/core_behavior_spec.md) — single-page rundown of ordering,
|
|
120
|
+
streaming, cancellation, deadline, and fan-in invariants with pointers to regression
|
|
121
|
+
tests.
|
|
122
|
+
|
|
84
123
|
---
|
|
85
124
|
|
|
86
125
|
## ✨ Why PenguiFlow?
|
|
@@ -168,6 +207,10 @@ print(out.payload) # PackOut(...)
|
|
|
168
207
|
await flow.stop()
|
|
169
208
|
```
|
|
170
209
|
|
|
210
|
+
> **Opt-in distribution:** pass `state_store=` and/or `message_bus=` when calling
|
|
211
|
+
> `penguiflow.core.create(...)` to persist trace history and publish floe traffic
|
|
212
|
+
> without changing node logic.
|
|
213
|
+
|
|
171
214
|
---
|
|
172
215
|
|
|
173
216
|
## 🧭 Design Principles
|
|
@@ -222,6 +265,60 @@ sacrificing backpressure or ordering guarantees. The helper wraps the payload i
|
|
|
222
265
|
increments per-stream sequence numbers. See `tests/test_streaming.py` and
|
|
223
266
|
`examples/streaming_llm/` for an end-to-end walk-through.
|
|
224
267
|
|
|
268
|
+
### Remote orchestration
|
|
269
|
+
|
|
270
|
+
Phase 2 introduces `RemoteNode` and the `RemoteTransport` protocol so flows can delegate
|
|
271
|
+
work to remote agents (e.g., the A2A JSON-RPC/SSE ecosystem) without changing existing
|
|
272
|
+
nodes. The helper records remote bindings via the `StateStore`, mirrors streaming
|
|
273
|
+
partials back into the graph, and propagates per-trace cancellation to remote tasks via
|
|
274
|
+
`RemoteTransport.cancel`. See `tests/test_remote.py` for reference in-memory transports.
|
|
275
|
+
|
|
276
|
+
### Exposing a flow over A2A
|
|
277
|
+
|
|
278
|
+
Install the optional extra to expose PenguiFlow as an A2A-compatible FastAPI service:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
pip install "penguiflow[a2a-server]"
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
Create the adapter and mount the routes:
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
from penguiflow import Message, Node, create
|
|
288
|
+
from penguiflow_a2a import A2AAgentCard, A2AServerAdapter, A2ASkill, create_a2a_app
|
|
289
|
+
|
|
290
|
+
async def orchestrate(message: Message, ctx):
|
|
291
|
+
await ctx.emit_chunk(parent=message, text="thinking...")
|
|
292
|
+
return {"result": "done"}
|
|
293
|
+
|
|
294
|
+
node = Node(orchestrate, name="main")
|
|
295
|
+
flow = create(node.to())
|
|
296
|
+
|
|
297
|
+
card = A2AAgentCard(
|
|
298
|
+
name="Main Agent",
|
|
299
|
+
description="Primary entrypoint for orchestration",
|
|
300
|
+
version="2.1.0",
|
|
301
|
+
skills=[A2ASkill(name="orchestrate", description="Handles orchestration")],
|
|
302
|
+
)
|
|
303
|
+
|
|
304
|
+
adapter = A2AServerAdapter(
|
|
305
|
+
flow,
|
|
306
|
+
agent_card=card,
|
|
307
|
+
agent_url="https://agent.example",
|
|
308
|
+
)
|
|
309
|
+
app = create_a2a_app(adapter)
|
|
310
|
+
```
|
|
311
|
+
|
|
312
|
+
The generated FastAPI app implements:
|
|
313
|
+
|
|
314
|
+
* `GET /agent` for discovery (Agent Card)
|
|
315
|
+
* `POST /message/send` for unary execution
|
|
316
|
+
* `POST /message/stream` for SSE streaming
|
|
317
|
+
* `POST /tasks/cancel` to mirror cancellation into PenguiFlow traces
|
|
318
|
+
|
|
319
|
+
`A2AServerAdapter` reuses the runtime's `StateStore` hooks, so bindings between trace IDs
|
|
320
|
+
and external `taskId`/`contextId` pairs are persisted automatically.
|
|
321
|
+
|
|
225
322
|
### Reliability & guardrails
|
|
226
323
|
|
|
227
324
|
PenguiFlow enforces reliability boundaries out of the box:
|
|
@@ -272,6 +369,70 @@ The new `penguiflow.testkit` module keeps unit tests tiny:
|
|
|
272
369
|
The harness is covered by `tests/test_testkit.py` and demonstrated in
|
|
273
370
|
`examples/testkit_demo/`.
|
|
274
371
|
|
|
372
|
+
### JSON-only ReAct planner (Phase A)
|
|
373
|
+
|
|
374
|
+
Phase A introduces a lightweight planner loop that keeps PenguiFlow typed and
|
|
375
|
+
deterministic:
|
|
376
|
+
|
|
377
|
+
* `penguiflow.catalog.NodeSpec` + `build_catalog` turn registered nodes into
|
|
378
|
+
tool descriptors with JSON Schemas derived from your Pydantic models.
|
|
379
|
+
* `penguiflow.planner.ReactPlanner` drives a JSON-only ReAct loop over those
|
|
380
|
+
descriptors, validating every LLM action with Pydantic and replaying invalid
|
|
381
|
+
steps to request corrections.
|
|
382
|
+
* LiteLLM stays optional—install `penguiflow[planner]` or inject a custom
|
|
383
|
+
`llm_client` for deterministic/offline runs.
|
|
384
|
+
|
|
385
|
+
See `examples/react_minimal/` for a stubbed end-to-end run.
|
|
386
|
+
|
|
387
|
+
### Trajectory summarisation & pause/resume (Phase B)
|
|
388
|
+
|
|
389
|
+
Phase B adds the tools you need for longer-running, approval-driven flows:
|
|
390
|
+
|
|
391
|
+
* **Token-aware summaries** — `Trajectory.compress()` keeps a compact state and
|
|
392
|
+
the planner can route summaries through a cheaper `summarizer_llm` before
|
|
393
|
+
asking for the next action.
|
|
394
|
+
* **`PlannerPause` contract** — nodes can call `await ctx.pause(...)` to return a
|
|
395
|
+
typed pause payload. Resume the run later with `ReactPlanner.resume(token, user_input=...)`.
|
|
396
|
+
* **Developer hints** — pass `planning_hints={...}` to enforce disallowed tools,
|
|
397
|
+
preferred ordering, or parallelism ceilings.
|
|
398
|
+
|
|
399
|
+
All three features are exercised in `examples/react_pause_resume/`, which runs
|
|
400
|
+
entirely offline with stubbed LLM responses.
|
|
401
|
+
|
|
402
|
+
### Adaptive re-planning & budgets (Phase C)
|
|
403
|
+
|
|
404
|
+
Phase C closes the loop when things go sideways:
|
|
405
|
+
|
|
406
|
+
* **Structured failure feedback** — if a tool raises after exhausting its retries,
|
|
407
|
+
the planner records `{failure: {node, args, error_code, suggestion}}` and feeds
|
|
408
|
+
it back to the LLM, prompting a constrained re-plan instead of aborting.
|
|
409
|
+
* **Hard guardrails** — configure wall-clock deadlines and hop budgets directly
|
|
410
|
+
on `ReactPlanner`; attempts beyond the allotted hops surface deterministic
|
|
411
|
+
violations and ultimately finish with `reason="budget_exhausted"` alongside a
|
|
412
|
+
constraint snapshot.
|
|
413
|
+
* **Typed exit reasons** — runs now finish with one of
|
|
414
|
+
`answer_complete`, `no_path`, or `budget_exhausted`, keeping downstream code
|
|
415
|
+
simple and machine-checkable.
|
|
416
|
+
|
|
417
|
+
The new `examples/react_replan/` sample shows a retrieval timeout automatically
|
|
418
|
+
recover via a cached index without leaving the JSON-only contract.
|
|
419
|
+
|
|
420
|
+
### Parallel fan-out & joins (Phase D)
|
|
421
|
+
|
|
422
|
+
Phase D lets the planner propose sets of independent tool calls and join them
|
|
423
|
+
without leaving the typed surface area:
|
|
424
|
+
|
|
425
|
+
* **Parallel `plan` blocks** — the LLM can return `{"plan": [...]}` actions
|
|
426
|
+
where each branch is validated against the catalog and executed concurrently.
|
|
427
|
+
* **Typed joins** — provide a `{"join": {"node": ...}}` descriptor and the
|
|
428
|
+
planner will aggregate results, auto-populate fields like `expect`, `results`,
|
|
429
|
+
or `failures`, and feed branch metadata through `ctx.meta` for the join node.
|
|
430
|
+
* **Deterministic telemetry** — branch errors, pauses, and joins are recorded as
|
|
431
|
+
structured observations so follow-up actions can re-plan or finish cleanly.
|
|
432
|
+
|
|
433
|
+
See `examples/react_parallel/` for a shard fan-out that merges responses in one
|
|
434
|
+
round-trip.
|
|
435
|
+
|
|
275
436
|
|
|
276
437
|
## 🧭 Repo Structure
|
|
277
438
|
|
|
@@ -478,9 +639,15 @@ docs or diagramming pipelines.
|
|
|
478
639
|
* **Structured `FlowEvent`s**: every node event carries `{ts, trace_id, node_name, event,
|
|
479
640
|
latency_ms, q_depth_in, q_depth_out, attempt}` plus a mutable `extra` map for custom
|
|
480
641
|
annotations.
|
|
642
|
+
* **Remote call telemetry**: `RemoteNode` executions emit extra metrics (latency, request
|
|
643
|
+
and response bytes, context/task identifiers, cancel reasons) so remote hops can be
|
|
644
|
+
traced end-to-end.
|
|
481
645
|
* **Middleware hooks**: subscribe observers (e.g., MLflow) to the structured `FlowEvent`
|
|
482
646
|
stream. See `examples/mlflow_metrics/` for an MLflow integration and
|
|
483
647
|
`examples/reliability_middleware/` for a concrete timeout + retry walkthrough.
|
|
648
|
+
* **`penguiflow-admin` CLI**: inspect or replay stored trace history from any configured
|
|
649
|
+
`StateStore` (`penguiflow-admin history <trace>` or `penguiflow-admin replay <trace>`)
|
|
650
|
+
when debugging distributed runs.
|
|
484
651
|
|
|
485
652
|
---
|
|
486
653
|
|
|
@@ -488,9 +655,9 @@ docs or diagramming pipelines.
|
|
|
488
655
|
|
|
489
656
|
- **In-process runtime**: there is no built-in distribution layer yet. Long-running CPU work should be delegated to your own pools or services.
|
|
490
657
|
- **Registry-driven typing**: nodes default to validation. Provide a `ModelRegistry` when calling `flow.run(...)` or set `validate="none"` explicitly for untyped hops.
|
|
491
|
-
- **Observability**: structured `FlowEvent` callbacks
|
|
492
|
-
third-party stacks (OTel, Prometheus, Datadog) remain
|
|
493
|
-
example for a lightweight pattern.
|
|
658
|
+
- **Observability**: structured `FlowEvent` callbacks and the `penguiflow-admin` CLI power
|
|
659
|
+
local debugging; integrations with third-party stacks (OTel, Prometheus, Datadog) remain
|
|
660
|
+
DIY. See the MLflow middleware example for a lightweight pattern.
|
|
494
661
|
- **Roadmap**: follow-up releases focus on optional distributed backends, deeper observability integrations, and additional playbook patterns. Contributions and proposals are welcome!
|
|
495
662
|
|
|
496
663
|
---
|
|
@@ -546,6 +713,8 @@ pytest -q
|
|
|
546
713
|
* `examples/streaming_llm/`: mock LLM emitting streaming chunks to an SSE sink.
|
|
547
714
|
* `examples/metadata_propagation/`: attaching and consuming `Message.meta` context.
|
|
548
715
|
* `examples/visualizer/`: exports Mermaid + DOT diagrams with loop/subflow annotations.
|
|
716
|
+
* `examples/react_minimal/`: JSON-only ReactPlanner loop with a stubbed LLM.
|
|
717
|
+
* `examples/react_pause_resume/`: Phase B planner features with pause/resume and developer hints.
|
|
549
718
|
|
|
550
719
|
---
|
|
551
720
|
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
penguiflow/__init__.py,sha256=Hsoc0UtuilT77kVd0VKPlgnliaRlHgkA3XYd_PvQOYw,2435
|
|
2
|
+
penguiflow/admin.py,sha256=093xFkE4bM_2ZhLrzhrEUKtmKHi_yVfMPyaGfwi1rcA,5382
|
|
3
|
+
penguiflow/bus.py,sha256=mb29509_n97A6zwC-6EDpYorfAWFSpwqsMu_WeZhLE8,732
|
|
4
|
+
penguiflow/catalog.py,sha256=z-Drf6PbEkvd65PcBvsVJZBBnM9GwT8ctcMdiIoQ5HY,4673
|
|
5
|
+
penguiflow/core.py,sha256=7w3fbyfQspt0aRt5nfDcr2kzPzX7Tf7O83v-U64DfHI,53960
|
|
6
|
+
penguiflow/debug.py,sha256=KPdpWbascsi1ghu-2HPqRORPM2iqkuV6qWyPc0mAalY,945
|
|
7
|
+
penguiflow/errors.py,sha256=mXpCqZ3zdvz7J7Dck_kcw2BGTIm9yrJAjxp_L8KMY7o,3419
|
|
8
|
+
penguiflow/metrics.py,sha256=KsxH9tUqrYfs3EyccLcM0-haYySAByq7RMnK7q61eRA,3989
|
|
9
|
+
penguiflow/middlewares.py,sha256=cB4SrRciNcKHyLanaMVsfMElt3El0LNCj_3dyik07x4,2864
|
|
10
|
+
penguiflow/node.py,sha256=0NOs3rU6t1tHNNwwJopqzM2ufGcp82JpzhckynWBRqs,3563
|
|
11
|
+
penguiflow/patterns.py,sha256=qtzRSNRKxV5_qEPXhffd15PuCZs0YnoGF80nNUsrcxw,5512
|
|
12
|
+
penguiflow/policies.py,sha256=3w8ionnpTyuA0ZCc3jPpB011L7_i1qlbiO6escY024s,4385
|
|
13
|
+
penguiflow/registry.py,sha256=1nR3J1A6jzuevH8EMn83vCkSnnNKgE28CCO6fXMA3wE,2001
|
|
14
|
+
penguiflow/remote.py,sha256=0-2aW48P8OB8KLEC_7_F_RHtzVJk3huyAMBGdXjmWeA,16426
|
|
15
|
+
penguiflow/state.py,sha256=fBY5d_48hR4XHWVG08FraaQ7u4IVPJwooewfVLmzu1Q,1773
|
|
16
|
+
penguiflow/streaming.py,sha256=RKMm4VfaDA2ceEM_pB2Cuhmpwtdcjj7og-kjXQQDcbc,3863
|
|
17
|
+
penguiflow/testkit.py,sha256=pIFYpu1RfJnW2mbGvUkPhMpL-xDAw0E959oTMxLkLh8,11806
|
|
18
|
+
penguiflow/types.py,sha256=Fl56-b7OwIEUbPMDD1CY09nbOG_tmBw3FUhioojeG5M,1503
|
|
19
|
+
penguiflow/viz.py,sha256=KbBb9kKoL223vj0NgJV_jo5ny-0RTc2gcSBACm0jG8w,5508
|
|
20
|
+
penguiflow-2.2.0.dist-info/licenses/LICENSE,sha256=JSvodvLXxSct_kI9IBsZOBpVKoESQTB_AGbkClwZ7HI,1065
|
|
21
|
+
penguiflow_a2a/__init__.py,sha256=JuK_ov06yS2H97D2OVXhgX8LcgdOqE3EujUPaDKaduc,342
|
|
22
|
+
penguiflow_a2a/server.py,sha256=VMBO-oGjB6Z9mtRBU0z7ZFGprDUC_kihZJukh3budbs,25932
|
|
23
|
+
penguiflow-2.2.0.dist-info/METADATA,sha256=Rp0MN4Yovee2HoJOKpio9PE8A2UBTNUsECAsXBt6lLk,28872
|
|
24
|
+
penguiflow-2.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
+
penguiflow-2.2.0.dist-info/entry_points.txt,sha256=F2KxANLEVGRbpWLmcHcvYrTVLWbKWdmk3VOe98a7t9I,59
|
|
26
|
+
penguiflow-2.2.0.dist-info/top_level.txt,sha256=K-fTwLA14n0u_LDxDBCV7FmeBnJffhTOtUbTtOymQns,26
|
|
27
|
+
penguiflow-2.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Optional A2A adapters for PenguiFlow."""
|
|
2
|
+
|
|
3
|
+
from .server import (
|
|
4
|
+
A2AAgentCard,
|
|
5
|
+
A2AMessagePayload,
|
|
6
|
+
A2AServerAdapter,
|
|
7
|
+
A2ASkill,
|
|
8
|
+
A2ATaskCancelRequest,
|
|
9
|
+
create_a2a_app,
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
"A2AAgentCard",
|
|
14
|
+
"A2ASkill",
|
|
15
|
+
"A2AMessagePayload",
|
|
16
|
+
"A2ATaskCancelRequest",
|
|
17
|
+
"A2AServerAdapter",
|
|
18
|
+
"create_a2a_app",
|
|
19
|
+
]
|