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
|
- `CapabilitySource` is the mutable runtime registry object; typed
|
||||||
`SourceStatus` and `SourceInventory` snapshots are the serializable domain
|
`SourceStatus` and `SourceInventory` snapshots are the serializable domain
|
||||||
projections used at boundaries such as `list_sources()`.
|
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.
|
- `WfMcpService.capability_sources` is the canonical in-memory registry.
|
||||||
- Planner node lookup reads `CapabilitySource.capabilities.node_specs`
|
- Planner node lookup reads `CapabilitySource.capabilities.node_specs`
|
||||||
directly; the old `SpecSource` compatibility layer has been removed.
|
directly; the old `SpecSource` compatibility layer has been removed.
|
||||||
|
|||||||
@@ -84,10 +84,14 @@ class PathExpr:
|
|||||||
def le(self, other: object) -> Expr:
|
def le(self, other: object) -> Expr:
|
||||||
return self._binary("le", other)
|
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)
|
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)
|
return self._binary("ne", other)
|
||||||
|
|
||||||
def __gt__(self, other: object) -> Expr:
|
def __gt__(self, other: object) -> Expr:
|
||||||
|
|||||||
@@ -34,5 +34,5 @@ class Workflow(BaseModel):
|
|||||||
|
|
||||||
def validate_structure(self) -> "ValidationReport":
|
def validate_structure(self) -> "ValidationReport":
|
||||||
"""Return all structural validation issues for this workflow."""
|
"""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))
|
return cast("ValidationReport", validation.validate_workflow(self))
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from .refs import CapabilityRef, SourceRef
|
|||||||
from .sources import (
|
from .sources import (
|
||||||
CapabilityBuckets,
|
CapabilityBuckets,
|
||||||
CapabilitySource,
|
CapabilitySource,
|
||||||
|
NodeSpecInventory,
|
||||||
SourceCapabilityInventory,
|
SourceCapabilityInventory,
|
||||||
SourceInventory,
|
SourceInventory,
|
||||||
SourceKind,
|
SourceKind,
|
||||||
@@ -16,6 +17,7 @@ __all__ = [
|
|||||||
"CapabilityBuckets",
|
"CapabilityBuckets",
|
||||||
"CapabilitySource",
|
"CapabilitySource",
|
||||||
"CapabilityRef",
|
"CapabilityRef",
|
||||||
|
"NodeSpecInventory",
|
||||||
"SourceCapabilityInventory",
|
"SourceCapabilityInventory",
|
||||||
"SourceInventory",
|
"SourceInventory",
|
||||||
"SourceKind",
|
"SourceKind",
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ from wf_core import ReducerSpec
|
|||||||
from wf_core.runtime.ops.merges import ReducerDefinition
|
from wf_core.runtime.ops.merges import ReducerDefinition
|
||||||
|
|
||||||
SourceKind = Literal["system", "connection"]
|
SourceKind = Literal["system", "connection"]
|
||||||
|
JsonObject = dict[str, Any]
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True, slots=True)
|
@dataclass(frozen=True, slots=True)
|
||||||
@@ -42,11 +43,24 @@ class SourcePermissionsSnapshot(BaseModel):
|
|||||||
mutates_auth: bool = False
|
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):
|
class SourceCapabilityInventory(BaseModel):
|
||||||
"""Serializable names owned by one source, grouped by capability kind."""
|
"""Serializable names owned by one source, grouped by capability kind."""
|
||||||
|
|
||||||
tools: tuple[str, ...] = ()
|
tools: tuple[str, ...] = ()
|
||||||
node_specs: tuple[str, ...] = ()
|
node_specs: tuple[str, ...] = ()
|
||||||
|
node_spec_details: tuple[NodeSpecInventory, ...] = ()
|
||||||
reducers: tuple[str, ...] = ()
|
reducers: tuple[str, ...] = ()
|
||||||
prompts: tuple[str, ...] = ()
|
prompts: tuple[str, ...] = ()
|
||||||
resources: tuple[str, ...] = ()
|
resources: tuple[str, ...] = ()
|
||||||
@@ -126,8 +140,29 @@ class CapabilitySource:
|
|||||||
capabilities=SourceCapabilityInventory(
|
capabilities=SourceCapabilityInventory(
|
||||||
tools=tuple(sorted(self.capabilities.tools)),
|
tools=tuple(sorted(self.capabilities.tools)),
|
||||||
node_specs=tuple(sorted(self.capabilities.node_specs)),
|
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)),
|
reducers=tuple(sorted(self.capabilities.reducers)),
|
||||||
prompts=tuple(sorted(self.capabilities.prompts)),
|
prompts=tuple(sorted(self.capabilities.prompts)),
|
||||||
resources=tuple(sorted(self.capabilities.resources)),
|
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)
|
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>
|
[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 (
|
from tests.rewrite.actions import (
|
||||||
CounterUp,
|
CounterUp,
|
||||||
RateChange,
|
RateChange,
|
||||||
@@ -14,8 +15,24 @@ from tests.rewrite.models import Input, State, Storage
|
|||||||
from wf_authoring.builder import WorkflowBuilder
|
from wf_authoring.builder import WorkflowBuilder
|
||||||
from wf_authoring.dsl.conditions import expr, state
|
from wf_authoring.dsl.conditions import expr, state
|
||||||
from wf_authoring.reducers.catalog import ReducerCatalog
|
from wf_authoring.reducers.catalog import ReducerCatalog
|
||||||
|
from wf_authoring.reducers.decorator import reducer
|
||||||
from wf_core.tokens import END
|
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.
|
# alr so we start with workflowbuilder.
|
||||||
|
|
||||||
gacha = WorkflowBuilder(
|
gacha = WorkflowBuilder(
|
||||||
|
|||||||
Reference in New Issue
Block a user