Spaces:
Build error
Build error
# Creating Components | |
## The minimal component | |
Components can be used to implement various functionalities like providing messages to the prompt, executing code, or interacting with external services. | |
*Component* is a class that inherits from `AgentComponent` OR implements one or more *protocols*. Every *protocol* inherits `AgentComponent`, so your class automatically becomes a *component* once you inherit any *protocol*. | |
```py | |
class MyComponent(AgentComponent): | |
pass | |
``` | |
This is already a valid component, but it doesn't do anything yet. To add some functionality to it, you need to implement one or more *protocols*. | |
Let's create a simple component that adds "Hello World!" message to the agent's prompt. To do this we need to implement `MessageProvider` *protocol* in our component. `MessageProvider` is an interface with `get_messages` method: | |
```py | |
# No longer need to inherit AgentComponent, because MessageProvider already does it | |
class HelloComponent(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_messages` will called by the agent each time it needs to build a new prompt and the yielded messages will be added accordingly. | |
## Passing data to and between components | |
Since components are regular classes you can pass data (including other components) to them via the `__init__` method. | |
For example we can pass a config object and then retrieve an API key from it when needed: | |
```py | |
class DataComponent(MessageProvider): | |
def __init__(self, config: Config): | |
self.config = config | |
def get_messages(self) -> Iterator[ChatMessage]: | |
if self.config.openai_credentials.api_key: | |
yield ChatMessage.system("API key found!") | |
else: | |
yield ChatMessage.system("API key not found!") | |
``` | |
!!! note | |
Component-specific configuration handling isn't implemented yet. | |
## Configuring components | |
Components can be configured using a pydantic model. | |
To make component configurable, it must inherit from `ConfigurableComponent[BM]` where `BM` is the configuration class inheriting from pydantic's `BaseModel`. | |
You should pass the configuration instance to the `ConfigurableComponent`'s `__init__` or set its `config` property directly. | |
Using configuration allows you to load confugration from a file, and also serialize and deserialize it easily for any agent. | |
To learn more about configuration, including storing sensitive information and serialization see [Component Configuration](./components.md#component-configuration). | |
```py | |
# Example component configuration | |
class UserGreeterConfiguration(BaseModel): | |
user_name: str | |
class UserGreeterComponent(MessageProvider, ConfigurableComponent[UserGreeterConfiguration]): | |
def __init__(self): | |
# Creating configuration instance | |
# You could also pass it to the component constructor | |
# e.g. `def __init__(self, config: UserGreeterConfiguration):` | |
config = UserGreeterConfiguration(user_name="World") | |
# Passing the configuration instance to the parent class | |
UserGreeterComponent.__init__(self, config) | |
# This has the same effect as the line above: | |
# self.config = UserGreeterConfiguration(user_name="World") | |
def get_messages(self) -> Iterator[ChatMessage]: | |
# You can use the configuration like a regular model | |
yield ChatMessage.system(f"Hello, {self.config.user_name}!") | |
``` | |
## Providing commands | |
To extend what an agent can do, you need to provide commands using `CommandProvider` protocol. For example to allow agent to multiply two numbers, you can create a component like this: | |
```py | |
class MultiplicatorComponent(CommandProvider): | |
def get_commands(self) -> Iterator[Command]: | |
# Yield the command so the agent can use it | |
yield self.multiply | |
@command( | |
parameters={ | |
"a": JSONSchema( | |
type=JSONSchema.Type.INTEGER, | |
description="The first number", | |
required=True, | |
), | |
"b": JSONSchema( | |
type=JSONSchema.Type.INTEGER, | |
description="The second number", | |
required=True, | |
)}) | |
def multiply(self, a: int, b: int) -> str: | |
""" | |
Multiplies two numbers. | |
Args: | |
a: First number | |
b: Second number | |
Returns: | |
Result of multiplication | |
""" | |
return str(a * b) | |
``` | |
To learn more about commands see [🛠️ Commands](./commands.md). | |
## Prompt structure | |
After components provided all necessary data, the agent needs to build the final prompt that will be send to a llm. | |
Currently, `PromptStrategy` (*not* a protocol) is responsible for building the final prompt. | |
If you want to change the way the prompt is built, you need to create a new `PromptStrategy` class, and then call relevant methods in your agent class. | |
You can have a look at the default strategy used by the AutoGPT Agent: [OneShotAgentPromptStrategy](https://github.com/Significant-Gravitas/AutoGPT/tree/master/classic/original_autogpt/agents/prompt_strategies/one_shot.py), and how it's used in the [Agent](https://github.com/Significant-Gravitas/AutoGPT/tree/master/classic/original_autogpt/agents/agent.py) (search for `self.prompt_strategy`). | |
## Example `UserInteractionComponent` | |
Let's create a slightly simplified version of the component that is used by the built-in agent. | |
It gives an ability for the agent to ask user for input in the terminal. | |
1. Create a class for the component that inherits from `CommandProvider`. | |
```py | |
class MyUserInteractionComponent(CommandProvider): | |
"""Provides commands to interact with the user.""" | |
pass | |
``` | |
2. Implement command method that will ask user for input and return it. | |
```py | |
def ask_user(self, question: str) -> str: | |
"""If you need more details or information regarding the given goals, | |
you can ask the user for input.""" | |
print(f"\nQ: {question}") | |
resp = input("A:") | |
return f"The user's answer: '{resp}'" | |
``` | |
3. The command needs to be decorated with `@command`. | |
```py | |
@command( | |
parameters={ | |
"question": JSONSchema( | |
type=JSONSchema.Type.STRING, | |
description="The question or prompt to the user", | |
required=True, | |
) | |
}, | |
) | |
def ask_user(self, question: str) -> str: | |
"""If you need more details or information regarding the given goals, | |
you can ask the user for input.""" | |
print(f"\nQ: {question}") | |
resp = input("A:") | |
return f"The user's answer: '{resp}'" | |
``` | |
4. We need to implement `CommandProvider`'s `get_commands` method to yield the command. | |
```py | |
def get_commands(self) -> Iterator[Command]: | |
yield self.ask_user | |
``` | |
5. Since agent isn't always running in the terminal or interactive mode, we need to disable this component by setting `self._enabled=False` when it's not possible to ask for user input. | |
```py | |
def __init__(self, interactive_mode: bool): | |
self.config = config | |
self._enabled = interactive_mode | |
``` | |
The final component should look like this: | |
```py | |
# 1. | |
class MyUserInteractionComponent(CommandProvider): | |
"""Provides commands to interact with the user.""" | |
# We pass config to check if we're in noninteractive mode | |
def __init__(self, interactive_mode: bool): | |
self.config = config | |
# 5. | |
self._enabled = interactive_mode | |
# 4. | |
def get_commands(self) -> Iterator[Command]: | |
# Yielding the command so the agent can use it | |
# This won't be yielded if the component is disabled | |
yield self.ask_user | |
# 3. | |
@command( | |
# We need to provide a schema for ALL the command parameters | |
parameters={ | |
"question": JSONSchema( | |
type=JSONSchema.Type.STRING, | |
description="The question or prompt to the user", | |
required=True, | |
) | |
}, | |
) | |
# 2. | |
# Command name will be its method name and description will be its docstring | |
def ask_user(self, question: str) -> str: | |
"""If you need more details or information regarding the given goals, | |
you can ask the user for input.""" | |
print(f"\nQ: {question}") | |
resp = input("A:") | |
return f"The user's answer: '{resp}'" | |
``` | |
Now if we want to use our user interaction *instead of* the default one we need to somehow remove the default one (if our agent inherits from `Agent` the default one is inherited) and add our own. We can simply override the `user_interaction` in `__init__` method: | |
```py | |
class MyAgent(Agent): | |
def __init__( | |
self, | |
settings: AgentSettings, | |
llm_provider: MultiProvider, | |
file_storage: FileStorage, | |
app_config: Config, | |
): | |
# Call the parent constructor to bring in the default components | |
super().__init__(settings, llm_provider, file_storage, app_config) | |
# Disable the default user interaction component by overriding it | |
self.user_interaction = MyUserInteractionComponent() | |
``` | |
Alternatively we can disable the default component by setting it to `None`: | |
```py | |
class MyAgent(Agent): | |
def __init__( | |
self, | |
settings: AgentSettings, | |
llm_provider: MultiProvider, | |
file_storage: FileStorage, | |
app_config: Config, | |
): | |
# Call the parent constructor to bring in the default components | |
super().__init__(settings, llm_provider, file_storage, app_config) | |
# Disable the default user interaction component | |
self.user_interaction = None | |
# Add our own component | |
self.my_user_interaction = MyUserInteractionComponent(app_config) | |
``` | |
## Learn more | |
The best place to see more examples is to look at the built-in components in the [classic/original_autogpt/components](https://github.com/Significant-Gravitas/AutoGPT/tree/master/classic/original_autogpt/components/) and [classic/original_autogpt/commands](https://github.com/Significant-Gravitas/AutoGPT/tree/master/classic/original_autogpt/commands/) directories. | |
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). | |