more model; now for serializing
This commit is contained in:
@@ -194,6 +194,10 @@ The code now has the first capability-source layer in place.
|
||||
- `CapabilitySource` is the mutable runtime registry object; typed
|
||||
`SourceStatus` and `SourceInventory` snapshots are the serializable domain
|
||||
projections used at boundaries such as `list_sources()`.
|
||||
- Executable `NodeSpec` objects stay inside runtime buckets because they hold
|
||||
Python callables. Inventory exposes `NodeSpecInventory` contracts instead:
|
||||
names, descriptions, outcomes, schemas, and execution flags, but never the
|
||||
wrapped function object itself.
|
||||
- `WfMcpService.capability_sources` is the canonical in-memory registry.
|
||||
- Planner node lookup reads `CapabilitySource.capabilities.node_specs`
|
||||
directly; the old `SpecSource` compatibility layer has been removed.
|
||||
|
||||
@@ -84,10 +84,14 @@ class PathExpr:
|
||||
def le(self, other: object) -> Expr:
|
||||
return self._binary("le", other)
|
||||
|
||||
def __eq__(self, other: object) -> Expr: # pyright: ignore[reportIncompatibleMethodOverride] # type: ignore[override] # ty: ignore[invalid-method-override]
|
||||
def __eq__( # pyright: ignore[reportIncompatibleMethodOverride] # type: ignore[override] # ty: ignore[invalid-method-override]
|
||||
self, other: object
|
||||
) -> Expr:
|
||||
return self._binary("eq", other)
|
||||
|
||||
def __ne__(self, other: object) -> Expr: # pyright: ignore[reportIncompatibleMethodOverride] # type: ignore[override] # ty: ignore[invalid-method-override]
|
||||
def __ne__( # pyright: ignore[reportIncompatibleMethodOverride] # type: ignore[override] # ty: ignore[invalid-method-override]
|
||||
self, other: object
|
||||
) -> Expr:
|
||||
return self._binary("ne", other)
|
||||
|
||||
def __gt__(self, other: object) -> Expr:
|
||||
|
||||
@@ -34,5 +34,5 @@ class Workflow(BaseModel):
|
||||
|
||||
def validate_structure(self) -> "ValidationReport":
|
||||
"""Return all structural validation issues for this workflow."""
|
||||
validation = import_module("wf_core.validation.core")
|
||||
import wf_core.validation.core as validation
|
||||
return cast("ValidationReport", validation.validate_workflow(self))
|
||||
|
||||
@@ -2,6 +2,7 @@ from .refs import CapabilityRef, SourceRef
|
||||
from .sources import (
|
||||
CapabilityBuckets,
|
||||
CapabilitySource,
|
||||
NodeSpecInventory,
|
||||
SourceCapabilityInventory,
|
||||
SourceInventory,
|
||||
SourceKind,
|
||||
@@ -16,6 +17,7 @@ __all__ = [
|
||||
"CapabilityBuckets",
|
||||
"CapabilitySource",
|
||||
"CapabilityRef",
|
||||
"NodeSpecInventory",
|
||||
"SourceCapabilityInventory",
|
||||
"SourceInventory",
|
||||
"SourceKind",
|
||||
|
||||
@@ -8,6 +8,7 @@ from wf_core import ReducerSpec
|
||||
from wf_core.runtime.ops.merges import ReducerDefinition
|
||||
|
||||
SourceKind = Literal["system", "connection"]
|
||||
JsonObject = dict[str, Any]
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
@@ -42,11 +43,24 @@ class SourcePermissionsSnapshot(BaseModel):
|
||||
mutates_auth: bool = False
|
||||
|
||||
|
||||
class NodeSpecInventory(BaseModel):
|
||||
"""Serializable public contract for one executable node spec."""
|
||||
|
||||
name: str
|
||||
description: str | None = None
|
||||
outcomes: tuple[str, ...]
|
||||
input_schema: JsonObject
|
||||
output_schema: JsonObject
|
||||
is_async: bool
|
||||
accepts_context: bool
|
||||
|
||||
|
||||
class SourceCapabilityInventory(BaseModel):
|
||||
"""Serializable names owned by one source, grouped by capability kind."""
|
||||
|
||||
tools: tuple[str, ...] = ()
|
||||
node_specs: tuple[str, ...] = ()
|
||||
node_spec_details: tuple[NodeSpecInventory, ...] = ()
|
||||
reducers: tuple[str, ...] = ()
|
||||
prompts: tuple[str, ...] = ()
|
||||
resources: tuple[str, ...] = ()
|
||||
@@ -126,8 +140,29 @@ class CapabilitySource:
|
||||
capabilities=SourceCapabilityInventory(
|
||||
tools=tuple(sorted(self.capabilities.tools)),
|
||||
node_specs=tuple(sorted(self.capabilities.node_specs)),
|
||||
node_spec_details=tuple(
|
||||
_node_spec_inventory(spec)
|
||||
for spec in sorted(
|
||||
self.capabilities.node_specs.values(),
|
||||
key=lambda spec: spec.name,
|
||||
)
|
||||
),
|
||||
reducers=tuple(sorted(self.capabilities.reducers)),
|
||||
prompts=tuple(sorted(self.capabilities.prompts)),
|
||||
resources=tuple(sorted(self.capabilities.resources)),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _node_spec_inventory(spec: NodeSpec[Any, Any]) -> NodeSpecInventory:
|
||||
"""Project one executable node spec into a serializable public contract."""
|
||||
node_def = spec.to_node_def()
|
||||
return NodeSpecInventory(
|
||||
name=spec.name,
|
||||
description=spec.description,
|
||||
outcomes=tuple(node_def.outcomes),
|
||||
input_schema=node_def.input_schema.model_dump(mode="json"),
|
||||
output_schema=node_def.output_schema.model_dump(mode="json"),
|
||||
is_async=spec.is_async,
|
||||
accepts_context=spec.accepts_context,
|
||||
)
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from wf_authoring import node
|
||||
from wf_platform import CapabilityBuckets, CapabilitySource, NodeSpecInventory
|
||||
|
||||
|
||||
class EchoInput(BaseModel):
|
||||
text: str = Field(description="Text to echo.")
|
||||
|
||||
|
||||
class EchoOutput(BaseModel):
|
||||
echoed: str
|
||||
|
||||
|
||||
@node
|
||||
def echo(payload: EchoInput) -> EchoOutput:
|
||||
"""Echo one text field."""
|
||||
return EchoOutput(echoed=payload.text)
|
||||
|
||||
|
||||
def test_source_inventory_exposes_serializable_node_spec_details() -> None:
|
||||
source = CapabilitySource(
|
||||
id="demo.personal",
|
||||
kind="connection",
|
||||
capabilities=CapabilityBuckets(node_specs={echo.name: echo}),
|
||||
)
|
||||
|
||||
inventory = source.as_inventory()
|
||||
detail = inventory.capabilities.node_spec_details[0]
|
||||
dumped = inventory.model_dump(mode="json")
|
||||
|
||||
assert isinstance(detail, NodeSpecInventory)
|
||||
assert detail.name == echo.name
|
||||
assert detail.description == "Echo one text field."
|
||||
assert detail.outcomes == ("ok",)
|
||||
assert detail.input_schema["properties"]["text"]["description"] == "Text to echo."
|
||||
assert "fn" not in dumped["capabilities"]["node_spec_details"][0]
|
||||
@@ -3,3 +3,7 @@
|
||||
so you know im using ts and influencing its design. im not just an user (lowk i am)
|
||||
|
||||
[llgd]: <https://git.ldlda.com/lda/langgraph-demo>
|
||||
|
||||
## to agents
|
||||
|
||||
these are not to be massively shuffled. you can help me; inform your edits.
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from tests.authoring.test_reducers import modulo_add
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from tests.rewrite.actions import (
|
||||
CounterUp,
|
||||
RateChange,
|
||||
@@ -14,8 +15,24 @@ from tests.rewrite.models import Input, State, Storage
|
||||
from wf_authoring.builder import WorkflowBuilder
|
||||
from wf_authoring.dsl.conditions import expr, state
|
||||
from wf_authoring.reducers.catalog import ReducerCatalog
|
||||
from wf_authoring.reducers.decorator import reducer
|
||||
from wf_core.tokens import END
|
||||
|
||||
|
||||
class ModuloConfig(BaseModel):
|
||||
modulus: int = Field(gt=0)
|
||||
|
||||
|
||||
@reducer(name="wf.std.modulo_add", config_model=ModuloConfig)
|
||||
def modulo_add(
|
||||
current: int | None,
|
||||
incoming: int,
|
||||
config: ModuloConfig,
|
||||
) -> int:
|
||||
"""Add incoming values modulo a configured positive integer."""
|
||||
return ((current or 0) + incoming) % config.modulus
|
||||
|
||||
|
||||
# alr so we start with workflowbuilder.
|
||||
|
||||
gacha = WorkflowBuilder(
|
||||
|
||||
Reference in New Issue
Block a user