mirror of
https://github.com/langgenius/dify.git
synced 2026-05-25 19:00:43 -04:00
Co-authored-by: Copilot <copilot@github.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
188 lines
6.6 KiB
Markdown
188 lines
6.6 KiB
Markdown
# Agenton user guide
|
|
|
|
Agenton composes reusable graph plans from `LayerNode`s and `LayerProvider`s.
|
|
The core is state-only: a `Compositor` stores no live layer instances, clients,
|
|
cleanup stacks, or run state. Each `Compositor.enter(...)` call creates a fresh
|
|
`CompositorRun` with new layer instances, direct dependency bindings, lifecycle
|
|
state, and an optional hydrated session snapshot.
|
|
|
|
## Config and runtime state
|
|
|
|
- **Graph config** is serializable topology: node `name`, provider `type`,
|
|
dependency mappings, and metadata. `LayerNodeConfig` deliberately contains no
|
|
layer config.
|
|
- **Per-run layer config** is passed to `Compositor.enter(configs=...)` as a
|
|
mapping keyed by node name. Providers validate each value with the layer's
|
|
`config_type` before any factory runs.
|
|
- **Runtime state** is serializable per-layer invocation state on
|
|
`layer.runtime_state`. Session snapshots persist only lifecycle state and this
|
|
model's JSON-safe data.
|
|
- **Live Python resources** such as clients, files, sockets, or process handles
|
|
stay outside Agenton core. Own them in application code or integration-specific
|
|
context managers that wrap compositor entry.
|
|
|
|
## Define a config-backed layer
|
|
|
|
Use a `LayerConfig` model for per-run config and inherit from a typed layer family
|
|
so `Layer.__init_subclass__` can infer schemas:
|
|
|
|
```python {test="skip" lint="skip"}
|
|
from dataclasses import dataclass
|
|
|
|
from pydantic import ConfigDict
|
|
from typing_extensions import Self, override
|
|
|
|
from agenton.layers import LayerConfig, NoLayerDeps, PlainLayer
|
|
|
|
|
|
class GreetingConfig(LayerConfig):
|
|
prefix: str
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class GreetingLayer(PlainLayer[NoLayerDeps, GreetingConfig]):
|
|
type_id = "example.greeting"
|
|
|
|
prefix: str
|
|
|
|
@classmethod
|
|
@override
|
|
def from_config(cls, config: GreetingConfig) -> Self:
|
|
return cls(prefix=config.prefix)
|
|
|
|
@property
|
|
@override
|
|
def prefix_prompts(self) -> list[str]:
|
|
return [self.prefix]
|
|
```
|
|
|
|
Omitted schema slots default to `EmptyLayerConfig` and `EmptyRuntimeState`.
|
|
Lifecycle hooks are no-argument methods on the layer instance; use `self.deps`
|
|
for dependencies and `self.runtime_state` for serializable mutable state.
|
|
|
|
## Live resources
|
|
|
|
Agenton does not own resource cleanup. Keep live resources in the surrounding
|
|
application and pass them to capability methods explicitly:
|
|
|
|
```python {test="skip" lint="skip"}
|
|
@dataclass(slots=True)
|
|
class ClientUserLayer(PlainLayer[NoLayerDeps]):
|
|
def make_client_user(self, *, http_client: httpx.AsyncClient) -> ClientUser:
|
|
return ClientUser(http_client)
|
|
|
|
|
|
compositor = Compositor([LayerNode("client_user", ClientUserLayer)])
|
|
async with httpx.AsyncClient() as http_client:
|
|
async with compositor.enter() as run:
|
|
layer = run.get_layer("client_user", ClientUserLayer)
|
|
user = layer.make_client_user(http_client=http_client)
|
|
```
|
|
|
|
This keeps deterministic cleanup at the integration boundary and leaves Agenton
|
|
snapshots limited to serializable runtime state.
|
|
|
|
## Build a compositor
|
|
|
|
Use providers for config-backed layers and pass per-run config at entry time:
|
|
|
|
```python {test="skip" lint="skip"}
|
|
from agenton.compositor import Compositor, CompositorConfig, LayerNodeConfig, LayerProvider
|
|
from agenton_collections.layers.plain import PromptLayer, PromptLayerConfig
|
|
|
|
|
|
providers = (
|
|
LayerProvider.from_layer_type(PromptLayer),
|
|
LayerProvider.from_layer_type(GreetingLayer),
|
|
)
|
|
compositor = Compositor.from_config(
|
|
CompositorConfig(
|
|
layers=[
|
|
LayerNodeConfig(name="prompt", type="plain.prompt"),
|
|
LayerNodeConfig(name="greeting", type="example.greeting"),
|
|
]
|
|
),
|
|
providers=providers,
|
|
)
|
|
|
|
async with compositor.enter(
|
|
configs={
|
|
"prompt": PromptLayerConfig(user="Answer with examples."),
|
|
"greeting": GreetingConfig(prefix="Hi"),
|
|
}
|
|
) as run:
|
|
prompts = run.prompts
|
|
```
|
|
|
|
Use `LayerProvider.from_factory(...)` when construction needs Python objects or
|
|
callables. Provider factories receive only validated config and must return a
|
|
fresh layer instance for every invocation. For node-specific construction with
|
|
`Compositor.from_config`, pass a `node_providers={"node_name": provider}` mapping
|
|
to override the provider selected by type id for that node.
|
|
|
|
## Dependencies
|
|
|
|
Layer dependencies bind direct layer instances onto `self.deps` for one run.
|
|
Dependency mappings use dependency field names as keys and compositor node names
|
|
as values:
|
|
|
|
```python {test="skip" lint="skip"}
|
|
class ModelDeps(LayerDeps):
|
|
plugin: PluginLayer
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class ModelLayer(PlainLayer[ModelDeps]):
|
|
def make_model(self) -> Model:
|
|
return self.deps.plugin.make_provider()
|
|
```
|
|
|
|
Optional dependencies are assigned `None` when absent. Missing required
|
|
dependencies, unknown dependency keys, and dependency targets with the wrong layer
|
|
type fail before lifecycle hooks run.
|
|
|
|
## System prompts, user prompts, and tools
|
|
|
|
Layers expose four authoring 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.
|
|
- `tools`: tool entries collected in layer order.
|
|
|
|
`PromptLayer` accepts `prefix`, `user`, and `suffix` config fields. Aggregation is
|
|
available on the active `CompositorRun` as `run.prompts`, `run.user_prompts`, and
|
|
`run.tools`. For pydantic-ai, import
|
|
`agenton_collections.transformers.pydantic_ai.PYDANTIC_AI_TRANSFORMERS` and pass
|
|
it to `Compositor(...)` or `Compositor.from_config(...)` so tagged layer items are
|
|
converted to Pydantic AI prompt, user prompt, and tool values.
|
|
|
|
## Session snapshot and restore
|
|
|
|
Core Agenton run slots default to delete-on-exit. Call `run.suspend_on_exit()` or
|
|
`run.suspend_layer_on_exit(name)` inside the active context when the next snapshot
|
|
should be resumable:
|
|
|
|
```python {test="skip" lint="skip"}
|
|
async with compositor.enter(configs=configs) as run:
|
|
run.suspend_on_exit()
|
|
|
|
snapshot = run.session_snapshot
|
|
async with compositor.enter(configs=configs, session_snapshot=snapshot) as restored_run:
|
|
restored_layer = restored_run.get_layer("stateful", StatefulLayer)
|
|
```
|
|
|
|
`run.session_snapshot` is populated after context exit. Snapshots include ordered
|
|
layer names, non-active lifecycle states, and JSON-safe runtime state only. Active
|
|
state is rejected at the DTO boundary, and closed layers cannot be entered again.
|
|
To resume, pass the snapshot to a later `Compositor.enter(...)` call with the same
|
|
layer names and order.
|
|
|
|
See also:
|
|
|
|
- `examples/agenton/agenton_examples/basics.py`
|
|
- `examples/agenton/agenton_examples/pydantic_ai_bridge.py`
|
|
- `examples/agenton/agenton_examples/session_snapshot.py`
|