Files
dify/dify-agent/docs/agenton/guide/index.md
盐粒 Yanli 55f95dbc36 feat(agent): init agent server (#36087)
Co-authored-by: Copilot <copilot@github.com>
Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
2026-05-14 06:04:44 +00:00

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`