penguiflow 1.0.3__py3-none-any.whl → 2.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.
Potentially problematic release.
This version of penguiflow might be problematic. Click here for more details.
- penguiflow/__init__.py +45 -3
- penguiflow/admin.py +174 -0
- penguiflow/bus.py +30 -0
- penguiflow/core.py +941 -57
- penguiflow/errors.py +113 -0
- penguiflow/metrics.py +105 -0
- penguiflow/middlewares.py +6 -7
- penguiflow/patterns.py +47 -5
- penguiflow/policies.py +149 -0
- penguiflow/remote.py +486 -0
- penguiflow/state.py +64 -0
- penguiflow/streaming.py +142 -0
- penguiflow/testkit.py +269 -0
- penguiflow/types.py +15 -1
- penguiflow/viz.py +133 -24
- penguiflow-2.1.0.dist-info/METADATA +646 -0
- penguiflow-2.1.0.dist-info/RECORD +25 -0
- penguiflow-2.1.0.dist-info/entry_points.txt +2 -0
- penguiflow-2.1.0.dist-info/top_level.txt +2 -0
- penguiflow_a2a/__init__.py +19 -0
- penguiflow_a2a/server.py +695 -0
- penguiflow-1.0.3.dist-info/METADATA +0 -425
- penguiflow-1.0.3.dist-info/RECORD +0 -13
- penguiflow-1.0.3.dist-info/top_level.txt +0 -1
- {penguiflow-1.0.3.dist-info → penguiflow-2.1.0.dist-info}/WHEEL +0 -0
- {penguiflow-1.0.3.dist-info → penguiflow-2.1.0.dist-info}/licenses/LICENSE +0 -0
penguiflow/__init__.py
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
+
from . import testkit
|
|
6
|
+
from .bus import BusEnvelope, MessageBus
|
|
5
7
|
from .core import (
|
|
6
8
|
DEFAULT_QUEUE_MAXSIZE,
|
|
7
9
|
Context,
|
|
@@ -10,12 +12,29 @@ from .core import (
|
|
|
10
12
|
call_playbook,
|
|
11
13
|
create,
|
|
12
14
|
)
|
|
15
|
+
from .errors import FlowError, FlowErrorCode
|
|
16
|
+
from .metrics import FlowEvent
|
|
13
17
|
from .middlewares import Middleware
|
|
14
18
|
from .node import Node, NodePolicy
|
|
15
19
|
from .patterns import join_k, map_concurrent, predicate_router, union_router
|
|
20
|
+
from .policies import DictRoutingPolicy, RoutingPolicy, RoutingRequest
|
|
16
21
|
from .registry import ModelRegistry
|
|
17
|
-
from .
|
|
18
|
-
|
|
22
|
+
from .remote import (
|
|
23
|
+
RemoteCallRequest,
|
|
24
|
+
RemoteCallResult,
|
|
25
|
+
RemoteNode,
|
|
26
|
+
RemoteStreamEvent,
|
|
27
|
+
RemoteTransport,
|
|
28
|
+
)
|
|
29
|
+
from .state import RemoteBinding, StateStore, StoredEvent
|
|
30
|
+
from .streaming import (
|
|
31
|
+
chunk_to_ws_json,
|
|
32
|
+
emit_stream_events,
|
|
33
|
+
format_sse_event,
|
|
34
|
+
stream_flow,
|
|
35
|
+
)
|
|
36
|
+
from .types import WM, FinalAnswer, Headers, Message, PlanStep, StreamChunk, Thought
|
|
37
|
+
from .viz import flow_to_dot, flow_to_mermaid
|
|
19
38
|
|
|
20
39
|
__all__ = [
|
|
21
40
|
"__version__",
|
|
@@ -27,9 +46,15 @@ __all__ = [
|
|
|
27
46
|
"NodePolicy",
|
|
28
47
|
"ModelRegistry",
|
|
29
48
|
"Middleware",
|
|
49
|
+
"FlowEvent",
|
|
50
|
+
"FlowError",
|
|
51
|
+
"FlowErrorCode",
|
|
52
|
+
"MessageBus",
|
|
53
|
+
"BusEnvelope",
|
|
30
54
|
"call_playbook",
|
|
31
55
|
"Headers",
|
|
32
56
|
"Message",
|
|
57
|
+
"StreamChunk",
|
|
33
58
|
"PlanStep",
|
|
34
59
|
"Thought",
|
|
35
60
|
"WM",
|
|
@@ -38,8 +63,25 @@ __all__ = [
|
|
|
38
63
|
"join_k",
|
|
39
64
|
"predicate_router",
|
|
40
65
|
"union_router",
|
|
66
|
+
"DictRoutingPolicy",
|
|
67
|
+
"RoutingPolicy",
|
|
68
|
+
"RoutingRequest",
|
|
69
|
+
"format_sse_event",
|
|
70
|
+
"chunk_to_ws_json",
|
|
71
|
+
"stream_flow",
|
|
72
|
+
"emit_stream_events",
|
|
41
73
|
"flow_to_mermaid",
|
|
74
|
+
"flow_to_dot",
|
|
42
75
|
"create",
|
|
76
|
+
"testkit",
|
|
77
|
+
"StateStore",
|
|
78
|
+
"StoredEvent",
|
|
79
|
+
"RemoteBinding",
|
|
80
|
+
"RemoteTransport",
|
|
81
|
+
"RemoteCallRequest",
|
|
82
|
+
"RemoteCallResult",
|
|
83
|
+
"RemoteStreamEvent",
|
|
84
|
+
"RemoteNode",
|
|
43
85
|
]
|
|
44
86
|
|
|
45
|
-
__version__ = "1.0
|
|
87
|
+
__version__ = "2.1.0"
|
penguiflow/admin.py
ADDED
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Developer CLI helpers for inspecting PenguiFlow trace history."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import asyncio
|
|
7
|
+
import importlib
|
|
8
|
+
import json
|
|
9
|
+
import sys
|
|
10
|
+
from collections.abc import Callable, Sequence
|
|
11
|
+
from typing import Any
|
|
12
|
+
|
|
13
|
+
from .state import StateStore, StoredEvent
|
|
14
|
+
|
|
15
|
+
__all__ = ["load_state_store", "render_events", "main"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class _Args(argparse.Namespace):
|
|
19
|
+
handler: Callable[[_Args], Any]
|
|
20
|
+
state_store: str
|
|
21
|
+
trace_id: str
|
|
22
|
+
tail: int | None
|
|
23
|
+
delay: float
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def _resolve_factory(spec: str) -> Callable[[], Any]:
|
|
27
|
+
module_name, _, attr = spec.partition(":")
|
|
28
|
+
if not module_name or not attr:
|
|
29
|
+
raise ValueError(
|
|
30
|
+
"state store spec must be in the form 'package.module:callable'"
|
|
31
|
+
)
|
|
32
|
+
module = importlib.import_module(module_name)
|
|
33
|
+
try:
|
|
34
|
+
factory = getattr(module, attr)
|
|
35
|
+
except AttributeError as exc: # pragma: no cover - defensive guard
|
|
36
|
+
raise ValueError(f"{spec!r} does not resolve to a callable") from exc
|
|
37
|
+
if not callable(factory):
|
|
38
|
+
raise TypeError(f"{spec!r} resolved to {type(factory)!r}, not a callable")
|
|
39
|
+
return factory
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
async def load_state_store(spec: str) -> StateStore:
|
|
43
|
+
"""Instantiate a :class:`StateStore` from ``module:callable`` spec."""
|
|
44
|
+
|
|
45
|
+
factory = _resolve_factory(spec)
|
|
46
|
+
instance = factory()
|
|
47
|
+
if asyncio.iscoroutine(instance):
|
|
48
|
+
instance = await instance
|
|
49
|
+
required = ("save_event", "load_history", "save_remote_binding")
|
|
50
|
+
if not all(hasattr(instance, attr) for attr in required): # pragma: no cover
|
|
51
|
+
raise TypeError(
|
|
52
|
+
"StateStore factories must implement "
|
|
53
|
+
"save_event/load_history/save_remote_binding"
|
|
54
|
+
)
|
|
55
|
+
return instance
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _trim_events(events: Sequence[StoredEvent], tail: int | None) -> list[StoredEvent]:
|
|
59
|
+
items = list(events)
|
|
60
|
+
if tail is None:
|
|
61
|
+
return items
|
|
62
|
+
if tail <= 0:
|
|
63
|
+
return []
|
|
64
|
+
return items[-tail:]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def render_events(
|
|
68
|
+
events: Sequence[StoredEvent], *, tail: int | None = None
|
|
69
|
+
) -> list[str]:
|
|
70
|
+
"""Return JSON line representations of ``events`` (optionally tail-truncated)."""
|
|
71
|
+
|
|
72
|
+
trimmed = _trim_events(events, tail)
|
|
73
|
+
lines: list[str] = []
|
|
74
|
+
for event in trimmed:
|
|
75
|
+
payload = dict(event.payload)
|
|
76
|
+
payload.setdefault("event", event.kind)
|
|
77
|
+
payload.setdefault("trace_id", event.trace_id)
|
|
78
|
+
payload.setdefault("node_name", event.node_name)
|
|
79
|
+
payload.setdefault("node_id", event.node_id)
|
|
80
|
+
payload.setdefault("ts", event.ts)
|
|
81
|
+
lines.append(json.dumps(payload, sort_keys=True, default=str))
|
|
82
|
+
return lines
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
async def _cmd_history(args: _Args) -> None:
|
|
86
|
+
store = await load_state_store(args.state_store)
|
|
87
|
+
events = await store.load_history(args.trace_id)
|
|
88
|
+
for line in render_events(events, tail=args.tail):
|
|
89
|
+
print(line)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
async def _cmd_replay(args: _Args) -> None:
|
|
93
|
+
store = await load_state_store(args.state_store)
|
|
94
|
+
events = _trim_events(await store.load_history(args.trace_id), args.tail)
|
|
95
|
+
total = len(events)
|
|
96
|
+
if not total:
|
|
97
|
+
print(f"# trace {args.trace_id} has no stored events")
|
|
98
|
+
return
|
|
99
|
+
print(f"# replay trace={args.trace_id} events={total}")
|
|
100
|
+
for event in events:
|
|
101
|
+
payload = render_events([event])[0]
|
|
102
|
+
print(payload)
|
|
103
|
+
if args.delay > 0:
|
|
104
|
+
await asyncio.sleep(args.delay)
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
108
|
+
parser = argparse.ArgumentParser(
|
|
109
|
+
prog="penguiflow-admin",
|
|
110
|
+
description=(
|
|
111
|
+
"Inspect PenguiFlow trace history via configured StateStore "
|
|
112
|
+
"adapters."
|
|
113
|
+
),
|
|
114
|
+
)
|
|
115
|
+
common = argparse.ArgumentParser(add_help=False)
|
|
116
|
+
common.add_argument(
|
|
117
|
+
"--state-store",
|
|
118
|
+
required=True,
|
|
119
|
+
help="Import path to a factory returning a StateStore (module:callable)",
|
|
120
|
+
)
|
|
121
|
+
common.add_argument(
|
|
122
|
+
"--tail",
|
|
123
|
+
type=int,
|
|
124
|
+
default=None,
|
|
125
|
+
help="Only show the last N events from the trace history.",
|
|
126
|
+
)
|
|
127
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
128
|
+
|
|
129
|
+
history = subparsers.add_parser(
|
|
130
|
+
"history",
|
|
131
|
+
parents=[common],
|
|
132
|
+
help="Print stored events for a trace as JSON lines.",
|
|
133
|
+
)
|
|
134
|
+
history.add_argument("trace_id", help="Trace identifier to inspect")
|
|
135
|
+
history.set_defaults(handler=_cmd_history)
|
|
136
|
+
|
|
137
|
+
replay = subparsers.add_parser(
|
|
138
|
+
"replay",
|
|
139
|
+
parents=[common],
|
|
140
|
+
help="Replay events with optional delay to mimic runtime emission.",
|
|
141
|
+
)
|
|
142
|
+
replay.add_argument("trace_id", help="Trace identifier to replay")
|
|
143
|
+
replay.add_argument(
|
|
144
|
+
"--delay",
|
|
145
|
+
type=float,
|
|
146
|
+
default=0.0,
|
|
147
|
+
help="Sleep duration (seconds) between events when replaying.",
|
|
148
|
+
)
|
|
149
|
+
replay.set_defaults(handler=_cmd_replay)
|
|
150
|
+
|
|
151
|
+
return parser
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
155
|
+
"""Entry point for the ``penguiflow-admin`` CLI."""
|
|
156
|
+
|
|
157
|
+
parser = _build_parser()
|
|
158
|
+
args = parser.parse_args(argv)
|
|
159
|
+
handler = getattr(args, "handler", None)
|
|
160
|
+
if handler is None: # pragma: no cover - argparse guard
|
|
161
|
+
parser.print_help()
|
|
162
|
+
return 1
|
|
163
|
+
|
|
164
|
+
try:
|
|
165
|
+
asyncio.run(handler(args))
|
|
166
|
+
except Exception as exc: # pragma: no cover - runtime guard
|
|
167
|
+
print(f"error: {exc}", file=sys.stderr)
|
|
168
|
+
return 1
|
|
169
|
+
return 0
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
if __name__ == "__main__": # pragma: no cover - manual invocation
|
|
173
|
+
raise SystemExit(main())
|
|
174
|
+
|
penguiflow/bus.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""Message bus protocol for distributed PenguiFlow edges."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from collections.abc import Mapping
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from typing import Any, Protocol
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass(slots=True)
|
|
11
|
+
class BusEnvelope:
|
|
12
|
+
"""Structured payload published to a :class:`MessageBus`."""
|
|
13
|
+
|
|
14
|
+
edge: str
|
|
15
|
+
source: str | None
|
|
16
|
+
target: str | None
|
|
17
|
+
trace_id: str | None
|
|
18
|
+
payload: Any
|
|
19
|
+
headers: Mapping[str, Any] | None
|
|
20
|
+
meta: Mapping[str, Any] | None
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MessageBus(Protocol):
|
|
24
|
+
"""Protocol for pluggable message bus adapters."""
|
|
25
|
+
|
|
26
|
+
async def publish(self, envelope: BusEnvelope) -> None:
|
|
27
|
+
"""Publish an envelope for downstream workers."""
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
__all__ = ["BusEnvelope", "MessageBus"]
|