Skip to content

Commit

Permalink
Draft component docs
Browse files Browse the repository at this point in the history
  • Loading branch information
kcze committed Apr 5, 2024
1 parent e5a62c8 commit db06efc
Show file tree
Hide file tree
Showing 10 changed files with 394 additions and 97 deletions.
4 changes: 2 additions & 2 deletions autogpts/autogpt/autogpt/agents/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -328,9 +328,9 @@ async def foreach_components(
except ProtocolError:
self._trace.append(
f"❌ {Fore.LIGHTRED_EX}{component.__class__.__name__}: "
f"PipelineError{Fore.RESET}"
f"ProtocolError{Fore.RESET}"
)
# Restart from the beginning on PipelineError
# Restart from the beginning on ProtocolError
# Revert to original parameters
args = self._selective_copy(original_args)
pipeline_attempts += 1
Expand Down
4 changes: 2 additions & 2 deletions autogpts/autogpt/autogpt/agents/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ def __init__(self, message: str = ""):
super().__init__(message)


class ComponentGroupError(ComponentError):
class PipelineError(ComponentError):
"""Error of a group of component types;
multiple pipelines."""
multiple protocols."""

def __init__(self, message: str = ""):
self.message = message
Expand Down
8 changes: 8 additions & 0 deletions docs/content/AutoGPT/component agent/advanced.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
### Other stuff
Debugging may be easier because we can inspect the exact components that were called and where the pipeline failed (current WIP pipeline):

![](../imgs/modular-pipeline.png)

Also that makes it possible to call component/pipeline/function again when failed and recover.

If it's necessary to get a component in a random place, agent provides generic, type safe `get_component(type[T]) -> T | None`
3 changes: 3 additions & 0 deletions docs/content/AutoGPT/component agent/agents.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# 🤖 Agents

is composed of components. It's responsible for executing pipelines and managing the components.
30 changes: 30 additions & 0 deletions docs/content/AutoGPT/component agent/commands.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# 🛠️ Commands

Commands a way for the agent to do anything; e.g. intercting with user or APIs and using tools. They are provided by components that implement the `CommandProvider` protocol.

```py
class CommandProvider(Protocol):
def get_commands(self) -> Iterator[Command]:
...
```

## Command decorator

The easiest way to provide a command is to use `command` decorator on a component method and then yield `Command.from_decorated_function(...)`. Each command needs a name, description and a parameter schema using `JSONSchema`. By default method name is used as a command name, and first part of docstring for the description (before `Args:` or `Returns:`) and schema can be provided in the decorator.

- Simplified
- Full

## Direct construction




```py
from autogpt.agents.components import Component
from autogpt.agents.protocols import CommandProvider
from autogpt.core.utils.json_schema import JSONSchema
from autogpt.command_decorator import command


```
105 changes: 105 additions & 0 deletions docs/content/AutoGPT/component agent/components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# 🧩 Components

Components are the building blocks of [🤖 Agents](./agents.md). They are classes inherited from `Component` that implement one or more [⚙️ Protocols](./protocols.md) that give agent additional abilities or processing.

Components assigned to attributes (fields) in agent's `__init__` are automatically discovered upon instantiation.
Each component can implement multiple protocols and can rely on other components if needed.

```py
from autogpt.agents import Agent
from autogpt.agents.components import Component

class MyAgent(Agent):
def __init__(self):
# These components will be automatically discovered and used
self.hello_component = HelloComponent()
# We pass HelloComponent to CalculatorComponent
self.calculator_component = CalculatorComponent(self.hello_component)
```
## Ordering components

For some protocols, the order of components is important because the latter ones may depend on the results of the former ones.

### Implicit order

Components can be ordered implicitly by the agent; each component can set `run_after` list to specify which components should run before it. This is useful when components rely on each other or need to be executed in a specific order. Otherwise, the order of components is alphabetical.

```py
# This component will run after HelloComponent
class CalculatorComponent(Component):
run_after = [HelloComponent]

def __init__(self, hello_component: HelloComponent):
self.hello_component = hello_component
```

### Explicit order

Sometimes it may be easier to order components explicitly by setting `self.components` list in the agent's `__init__` method. This way you can also ensure there's no circular dependencies and `run_after` is ignored.

> ⚠️ Be sure to include all components - by setting `self.components` list, you're overriding the default behavior of discovering components automatically. Since it's usually not intended agent will inform you in the terminal if some components were skipped.
```py
class MyAgent(Agent):
def __init__(self):
self.hello_component = HelloComponent()
self.calculator_component = CalculatorComponent(self.hello_component)
# Explicitly set components list
self.components = [self.hello_component, self.calculator_component]
```

## Disabling components

You can control which components are enabled by setting their `enabled` attribute. You can either provide a `bool` value or a `callable[[], bool]` that will be called each time the component is about to be executed. This way you can dynamically enable or disable components based on some conditions.
You can also provide a reason for disabling the component by setting `disabled_reason`. The reason will be visible in the debug information.

```py
class DisabledComponent(Component, MessageProvider):
def __init__(self):
# Disable this component
self.enabled = False
self.disabled_reason = "This component is disabled because of reasons."
# Or disable based on some condition
self.enabled = self.some_condition

# This method will never be called
def get_messages(self) -> Iterator[ChatMessage]:
yield ChatMessage.user("This message won't be seen!")

def some_condition(self) -> bool:
return False
```

If you don't want the component at all, you can just remove it from the agent's `__init__` method. If you want to remove components you inherit from the parent class you can set the relevant attribute to `None`:

```py
class MyAgent(Agent):
def __init__(self):
super().__init__(...)
# Disable WatchdogComponent that is in the parent class
self.watchdog = None

```

## Exceptions

Custom errors are provided which can be used to control the execution flow in case something went wrong. All those errors can be raised in protocol methods and will be caught by the agent.
By default agent will retry three times and then re-raise an exception if it's still not resolved. All passed arguments are automatically handled and the values are reverted when needed.
All errors accept an optional `str` message.

1. `ComponentError`: A single component failed to execute. Agent will retry the execution of the component.
2. `ProtocolError`: An entire protocol failed to execute. Agent will retry the execution of the protocol method for all components.
3. `PipelineError`: An entire pipeline failed to execute. Agent will retry the execution of the pipeline for all protocols. This isn't implemented yet.
4. `ComponentSystemError`: The highest-level error occurred in the component system. This isn't used.

**Example**

```py
from autogpt.agents.components import Component, ComponentError
from autogpt.agents.protocols import MessageProvider

# Example of raising an error
class MyComponent(Component, MessageProvider):
def get_messages(self) -> Iterator[ChatMessage]:
raise ComponentError("Component error!")
```
44 changes: 44 additions & 0 deletions docs/content/AutoGPT/component agent/creating-components.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Creating Components

## The minimal component

Let's create a simple component that adds "Hello World!" message to the agent prompt.
To create a component you just make a class that inherits from `Component`:

```py
# We recommend *Component suffix to make the type clear
class HelloComponent(Component):
pass
```

This is already a valid component but it doesn't have any functionality yet.
To make it do something we need to write a method that can be found and called by the agent. To put messages to the agent's prompt we need to implement `MessageProvider` Protocol in our component. `MessageProvider` is an interface with `get_messages` method:

```py
class HelloComponent(Component, MessageProvider):
def get_messages(self) -> Iterator[ChatMessage]:
yield ChatMessage.user("Hello World!")
```

Now we can add our component to an existing agent or create a new Agent class and add it there:

```py
class MyAgent(Agent):
self.hello_component = HelloComponent()
```

`get_messsages` will called by the agent each time it needs to build a new prompt and the yielded messages will be added accordingly.

## Full example



```py

```

## Learn more

Guide on how to extend the built-in agent and build your own: [🤖 Agents](./agents.md)
Order of some components matters, see [🧩 Components](./components.md) to learn more about components and how they can be customized.
To see built-in protocols with accompanying examples visit [⚙️ Protocols](./protocols.md).
17 changes: 17 additions & 0 deletions docs/content/AutoGPT/component agent/introduction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Component Agents

This guide explains the component-based architecture of AutoGPT agents. It's a new way of building agents that is more flexible and easier to extend. Components replace plugins with a more modular and composable system.

Agent is composed of *components*, and each `Component` implements a range of `Protocol`s (interfaces), each one providing a specific functionality, e.g. additional commands or messages. Each *protocol* is handled in a specific order, defined by the agent. This allows for a clear separation of concerns and a more modular design.

This system is simple, flexible, requires basically no configuration, and doesn't hide any data - anything can still be passed or accessed directly from or between components.

### Definitions & Guides

See quick guide [Creating Components](./creating-components.md) to get started! Or you can explore the following topics in detail:

- [🧩 Component](./components.md): a class that implements one or more *protocols*. It can be added to an agent to provide additional functionality.
- [⚙️ Protocol](./protocols.md): an interface that defines a set of methods that a component must implement. Protocols are used to group related functionality.
- [🛠️ Command](./commands.md):
- [🤖 Agent](./agents.md): a class that is composed of components. It's responsible for executing pipelines and managing the components.
- **Pipeline**: a sequence of method calls on components. Pipelines are used to execute a series of actions in a specific order. As of now there's no formal class for a pipeline, it's just a sequence of method calls on components. There are two default pipelines implemented in the default agent: `propose_action` and `execute`. See [🤖 Agent](./agents.md) to learn more.
Loading

0 comments on commit db06efc

Please sign in to comment.