diff --git a/dify-agent/README.md b/dify-agent/README.md index 08ba651da4..403d7327e7 100644 --- a/dify-agent/README.md +++ b/dify-agent/README.md @@ -1,3 +1,4 @@ # Dify Agent -Agenton documentation lives in [`docs/agenton/`](docs/agenton/). +Agenton documentation lives in [`docs/agenton/guide/`](docs/agenton/guide/) and +[`docs/agenton/api/`](docs/agenton/api/). diff --git a/dify-agent/docs/agenton/README.md b/dify-agent/docs/agenton/README.md index 120f3e3375..874e3cd44b 100644 --- a/dify-agent/docs/agenton/README.md +++ b/dify-agent/docs/agenton/README.md @@ -1,88 +1,6 @@ -# Agenton configuration and sessions +# Agenton documentation -Agenton composes shared `Layer` instances into a named graph. Treat layer -instances as reusable capability definitions: config and dependency declarations -belong on the layer class or instance, while per-session runtime values belong -on the `LayerControl` created for that layer in a `CompositorSession`. - -## Config, runtime state, and runtime handles - -- **Config** is serializable graph input. Config-constructible layers declare a - `type_id` and a Pydantic `config_type`; builders validate node config before - calling `Layer.from_config(validated_config)`. -- **Runtime state** is serializable per-layer/per-session state. Layers declare a - Pydantic `runtime_state_type`; session snapshots persist this model with - `model_dump(mode="json")`. -- **Runtime handles** are live Python objects such as clients, open files, or - process handles. Layers declare a Pydantic `runtime_handles_type` with - `arbitrary_types_allowed=True`. Handles are never serialized; resume hooks - should rehydrate them from runtime state. - -`Layer.__init_subclass__` infers `deps_type`, `config_type`, -`runtime_state_type`, and `runtime_handles_type` from generic base arguments -when possible. For example, `PlainLayer[NoLayerDeps, MyConfig, MyState, -MyHandles]` automatically installs those Pydantic schemas. Omitted schema slots -default to `EmptyLayerConfig`, `EmptyRuntimeState`, and `EmptyRuntimeHandles`. -Lifecycle hooks can annotate controls as `LayerControl[MyState, MyHandles]` to -get static checking and IDE completion for runtime state and handles. - -## Registry and builder - -Register config-constructible layers manually: - -```python -registry = LayerRegistry() -registry.register_layer(PromptLayer) # uses PromptLayer.type_id == "plain.prompt" -``` - -Use `CompositorBuilder` to mix serializable config nodes with live instances: - -```python -compositor = ( - CompositorBuilder(registry) - .add_config( - { - "layers": [ - { - "name": "prompt", - "type": "plain.prompt", - "config": {"prefix": "Hi", "user": "Answer with examples."}, - } - ] - } - ) - .add_instance(name="profile", layer=ObjectLayer(profile)) - .build() -) -``` - -Use `.add_instance()` for layers that require Python objects or callables, such -as `ObjectLayer`, `ToolsLayer`, and dynamic tool layers. - -## System prompts and user prompts - -Layers expose three prompt surfaces: - -- `prefix_prompts`: system prompt fragments collected in layer order. -- `suffix_prompts`: system prompt fragments collected in reverse layer order. -- `user_prompts`: user-message fragments collected in layer order. - -`PromptLayer` accepts `prefix`, `user`, and `suffix` config fields. For -pydantic-ai, `PYDANTIC_AI_TRANSFORMERS` maps `compositor.prompts` to system -prompt functions and `compositor.user_prompts` to values suitable for -`Agent.run(user_prompt=...)`. - -## Session snapshot and restore - -`Compositor.snapshot_session(session)` serializes non-active sessions, including -layer lifecycle state and runtime state. It rejects active sessions because live -handles cannot be snapshotted safely. Restore with -`Compositor.session_from_snapshot(snapshot)`; restored controls validate runtime -state with each layer schema and initialize empty runtime handles. Suspended -sessions resume through `on_context_resume`, where handles should be hydrated -from the restored runtime state. - -Create sessions with `Compositor.new_session()` or -`Compositor.session_from_snapshot()`. `Compositor.enter()` validates that every -session control uses the target layer's runtime state and handle schemas before -any lifecycle hook runs. +- [User guide](guide/) explains how to compose layers, register config-backed + plugins, use system/user prompts, and snapshot sessions. +- [API reference](api/) lists the public Agenton classes, methods, and extension + points. diff --git a/dify-agent/docs/agenton/api/README.md b/dify-agent/docs/agenton/api/README.md new file mode 100644 index 0000000000..a74fbd379c --- /dev/null +++ b/dify-agent/docs/agenton/api/README.md @@ -0,0 +1,183 @@ +# Agenton API reference + +This page summarizes the public Agenton API. Import paths are shown for the +symbols commonly used by layer authors and compositor callers. + +## Layers: `agenton.layers` + +### `Layer[DepsT, PromptT, UserPromptT, ToolT, ConfigT, RuntimeStateT, RuntimeHandlesT]` + +Framework-neutral base class for prompt/tool layers. + +Class attributes: + +- `type_id: str | None`: registry id for config-backed plugin layers. +- `config_type: type[BaseModel]`: Pydantic schema for serialized layer config. +- `runtime_state_type: type[BaseModel]`: Pydantic schema for snapshot-safe + per-session state. +- `runtime_handles_type: type[BaseModel]`: Pydantic schema for live runtime + handles; use `arbitrary_types_allowed=True` for client/process objects. +- `deps_type: type[LayerDeps]`: inferred from the layer generic base or declared + explicitly. + +Construction and dependency APIs: + +- `from_config(config: ConfigT) -> Self`: create a layer from schema-validated + config. The default implementation raises `TypeError`. +- `dependency_names() -> frozenset[str]`: dependency fields declared by + `deps_type`. +- `bind_deps(deps: Mapping[str, Layer | None]) -> None`: bind graph dependencies. +- `new_control(state=LifecycleState.NEW, runtime_state=None) -> LayerControl`: create + a schema-validated per-session control. + +Lifecycle hooks: + +- `on_context_create(control)` +- `on_context_resume(control)` +- `on_context_suspend(control)` +- `on_context_delete(control)` +- `enter(control)` / `lifecycle_enter(control)`: async context manager entry + surface. Override `enter()` only when a layer needs to wrap extra resources. + +Prompt/tool authoring surfaces: + +- `prefix_prompts -> Sequence[PromptT]` +- `suffix_prompts -> Sequence[PromptT]` +- `user_prompts -> Sequence[UserPromptT]` +- `tools -> Sequence[ToolT]` + +Aggregation adapters implemented by typed layer families: + +- `wrap_prompt(prompt: PromptT) -> object` +- `wrap_user_prompt(prompt: UserPromptT) -> object` +- `wrap_tool(tool: ToolT) -> object` + +### `LayerControl[RuntimeStateT, RuntimeHandlesT]` + +Per-layer, per-session lifecycle control. + +Fields: + +- `state: LifecycleState` +- `exit_intent: ExitIntent` +- `runtime_state: RuntimeStateT` +- `runtime_handles: RuntimeHandlesT` + +Methods: + +- `suspend_on_exit() -> None` +- `delete_on_exit() -> None` + +`runtime_state` is serialized in session snapshots. `runtime_handles` is never +serialized and should be rehydrated from runtime state in resume hooks. + +### Schema defaults and lifecycle enums + +- `EmptyLayerConfig` +- `EmptyRuntimeState` +- `EmptyRuntimeHandles` +- `LifecycleState`: `NEW`, `ACTIVE`, `SUSPENDED`, `CLOSED` +- `ExitIntent`: `DELETE`, `SUSPEND` + +### Typed layer families: `agenton.layers.types` + +- `PlainLayer[DepsT, ConfigT, RuntimeStateT, RuntimeHandlesT]` +- `PydanticAILayer[DepsT, AgentDepsT, ConfigT, RuntimeStateT, RuntimeHandlesT]` + +Tagged aggregate item types: + +- `PlainPromptType`, `PlainUserPromptType`, `PlainToolType` +- `PydanticAIPromptType`, `PydanticAIUserPromptType`, `PydanticAIToolType` +- `AllPromptTypes`, `AllUserPromptTypes`, `AllToolTypes` + +## Compositor: `agenton.compositor` + +### Config models + +- `LayerNodeConfig`: `name`, `type`, `config`, `deps`, `metadata` +- `CompositorConfig`: `schema_version`, `layers` + +Config nodes are pure serializable graph input. Use live instances for Python +objects and callables. + +### Registry + +`LayerRegistry` manually registers config-backed layer classes. + +- `register_layer(layer_type, type_id=None) -> None` +- `resolve(type_id) -> LayerDescriptor` +- `descriptors() -> Mapping[str, LayerDescriptor]` + +`LayerDescriptor` exposes `type_id`, `layer_type`, `config_type`, +`runtime_state_type`, and `runtime_handles_type`. + +### Builder + +`CompositorBuilder(registry)` mixes config-backed nodes and live instances. + +- `add_config(config) -> Self` +- `add_config_layer(name, type, config=None, deps=None) -> Self` +- `add_instance(name, layer, deps=None) -> Self` +- `build(prompt_transformer=None, user_prompt_transformer=None, tool_transformer=None) -> Compositor` + +### Compositor + +`Compositor[PromptT, ToolT, LayerPromptT, LayerToolT, UserPromptT, LayerUserPromptT]` +owns the ordered layer graph. + +Construction: + +- `Compositor(layers=..., deps_name_mapping=..., ...)` +- `Compositor.from_config(conf, registry=..., ...)` + +Aggregation properties: + +- `prompts -> list[PromptT]`: prefix prompts in layer order, suffix prompts in + reverse layer order, then optional `prompt_transformer`. +- `user_prompts -> list[UserPromptT]`: user prompts in layer order, then optional + `user_prompt_transformer`. +- `tools -> list[ToolT]`: tools in layer order, then optional `tool_transformer`. + +Session APIs: + +- `new_session() -> CompositorSession` +- `enter(session=None) -> AsyncIterator[CompositorSession]` +- `snapshot_session(session) -> CompositorSessionSnapshot` +- `session_from_snapshot(snapshot) -> CompositorSession` + +### Sessions and snapshots + +`CompositorSession` owns ordered layer controls. + +- `suspend_on_exit() -> None` +- `delete_on_exit() -> None` +- `layer(name) -> LayerControl` + +Snapshot models: + +- `LayerSessionSnapshot`: `name`, `state`, `runtime_state` +- `CompositorSessionSnapshot`: `schema_version`, `layers` + +Snapshots reject active sessions and exclude `runtime_handles` and `exit_intent`. + +## Collection layers and transformers + +### Plain layers: `agenton_collections.layers.plain` + +- `PromptLayer`: config-backed layer with `PromptLayerConfig(prefix, user, + suffix)` and `type_id = "plain.prompt"`. +- `ObjectLayer`: instance-only layer for Python objects. +- `ToolsLayer`: instance-only layer for callables. +- `DynamicToolsLayer`: instance-only layer for object-bound callables. + +### Pydantic AI bridge + +`agenton_collections.layers.pydantic_ai.PydanticAIBridgeLayer` exposes +pydantic-ai system prompts, user prompts, and tools while depending on an +`ObjectLayer` for `RunContext.deps`. + +`agenton_collections.transformers.PYDANTIC_AI_TRANSFORMERS` provides: + +- `prompt_transformer`: maps `compositor.prompts` to pydantic-ai system prompt functions. +- `user_prompt_transformer`: maps `compositor.user_prompts` to pydantic-ai `UserContent`. +- `tool_transformer`: maps `compositor.tools` to pydantic-ai tools. diff --git a/dify-agent/docs/agenton/guide/README.md b/dify-agent/docs/agenton/guide/README.md new file mode 100644 index 0000000000..de2b762d35 --- /dev/null +++ b/dify-agent/docs/agenton/guide/README.md @@ -0,0 +1,117 @@ +# Agenton user guide + +Agenton composes shared `Layer` instances into a named graph. Treat layer +instances as reusable capability definitions: config and dependency declarations +belong on the layer class or instance, while per-session runtime values belong +on the `LayerControl` created for that layer in a `CompositorSession`. + +## Config, runtime state, and runtime handles + +- **Config** is serializable graph input. Config-constructible layers declare a + `type_id` and a Pydantic `config_type`; builders validate node config before + calling `Layer.from_config(validated_config)`. +- **Runtime state** is serializable per-layer/per-session state. Layers declare a + Pydantic `runtime_state_type`; session snapshots persist this model with + `model_dump(mode="json")`. +- **Runtime handles** are live Python objects such as clients, open files, or + process handles. Layers declare a Pydantic `runtime_handles_type` with + `arbitrary_types_allowed=True`. Handles are never serialized; resume hooks + should rehydrate them from runtime state. + +## Define a config-backed layer + +Use a Pydantic model for config and pass it through the typed layer family so +`Layer.__init_subclass__` can infer the schema: + +```python +class GreetingConfig(BaseModel): + prefix: str + + model_config = ConfigDict(extra="forbid") + + +@dataclass +class GreetingLayer(PlainLayer[NoLayerDeps, GreetingConfig]): + type_id = "example.greeting" + prefix: str + + @classmethod + def from_config(cls, config: GreetingConfig) -> Self: + return cls(prefix=config.prefix) + + @property + def prefix_prompts(self) -> list[str]: + return [self.prefix] +``` + +Omitted schema slots default to `EmptyLayerConfig`, `EmptyRuntimeState`, and +`EmptyRuntimeHandles`. Lifecycle hooks can annotate controls as +`LayerControl[MyState, MyHandles]` to get static checking and IDE completion for +runtime state and handles. + +## Register layers and build a compositor + +Register config-constructible layers manually: + +```python +registry = LayerRegistry() +registry.register_layer(PromptLayer) # uses PromptLayer.type_id == "plain.prompt" +``` + +Use `CompositorBuilder` to mix serializable config nodes with live instances: + +```python +compositor = ( + CompositorBuilder(registry) + .add_config( + { + "layers": [ + { + "name": "prompt", + "type": "plain.prompt", + "config": {"prefix": "Hi", "user": "Answer with examples."}, + } + ] + } + ) + .add_instance(name="profile", layer=ObjectLayer(profile)) + .build() +) +``` + +Use `.add_instance()` for layers that require Python objects or callables, such +as `ObjectLayer`, `ToolsLayer`, and dynamic tool layers. + +## System prompts and user prompts + +Layers expose three prompt surfaces: + +- `prefix_prompts`: system prompt fragments collected in layer order. +- `suffix_prompts`: system prompt fragments collected in reverse layer order. +- `user_prompts`: user-message fragments collected in layer order. + +`PromptLayer` accepts `prefix`, `user`, and `suffix` config fields. For +pydantic-ai, `PYDANTIC_AI_TRANSFORMERS` maps `compositor.prompts` to system +prompt functions and `compositor.user_prompts` to values suitable for +`Agent.run(user_prompt=...)`. + +## Session snapshot and restore + +`Compositor.snapshot_session(session)` serializes non-active sessions, including +layer lifecycle state and runtime state. It rejects active sessions because live +handles cannot be snapshotted safely. Restore with +`Compositor.session_from_snapshot(snapshot)`; restored controls validate runtime +state with each layer schema and initialize empty runtime handles. Suspended +sessions resume through `on_context_resume`, where handles should be hydrated +from the restored runtime state. + +Create sessions with `Compositor.new_session()` or +`Compositor.session_from_snapshot()`. `Compositor.enter()` validates that every +session control uses the target layer's runtime state and handle schemas before +any lifecycle hook runs. + +See also: + +- `examples/agenton/basics.py` +- `examples/agenton/pydantic_ai_bridge.py` +- `examples/agenton/session_snapshot.py`