166 lines
5.4 KiB
Markdown
166 lines
5.4 KiB
Markdown
# ⚙️ Protocols
|
|
|
|
Protocols are *interfaces* implemented by [Components](./components.md) used to group related functionality. Each protocol needs to be handled explicitly by the agent at some point of the execution. We provide a comprehensive list of built-in protocols that are already handled in the built-in `Agent`, so when you inherit from the base agent all built-in protocols will work!
|
|
|
|
**Protocols are listed in the order of the default execution.**
|
|
|
|
## Order-independent protocols
|
|
|
|
Components implementing exclusively order-independent protocols can added in any order, including in-between ordered protocols.
|
|
|
|
### `DirectiveProvider`
|
|
|
|
Yields constraints, resources and best practices for the agent. This has no direct impact on other protocols; is purely informational and will be passed to a llm when the prompt is built.
|
|
|
|
```py
|
|
class DirectiveProvider(AgentComponent):
|
|
def get_constraints(self) -> Iterator[str]:
|
|
return iter([])
|
|
|
|
def get_resources(self) -> Iterator[str]:
|
|
return iter([])
|
|
|
|
def get_best_practices(self) -> Iterator[str]:
|
|
return iter([])
|
|
```
|
|
|
|
**Example** A web-search component can provide a resource information. Keep in mind that this actually doesn't allow the agent to access the internet. To do this a relevant `Command` needs to be provided.
|
|
|
|
```py
|
|
class WebSearchComponent(DirectiveProvider):
|
|
def get_resources(self) -> Iterator[str]:
|
|
yield "Internet access for searches and information gathering."
|
|
# We can skip "get_constraints" and "get_best_practices" if they aren't needed
|
|
```
|
|
|
|
### `CommandProvider`
|
|
|
|
Provides a command that can be executed by the agent.
|
|
|
|
```py
|
|
class CommandProvider(AgentComponent):
|
|
def get_commands(self) -> Iterator[Command]:
|
|
...
|
|
```
|
|
|
|
The easiest way to provide a command is to use `command` decorator on a component method and then yield the method. 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.
|
|
|
|
**Example** Calculator component that can perform multiplication. Agent is able to call this command if it's relevant to a current task and will see the returned result.
|
|
|
|
```py
|
|
from forge.agent import CommandProvider, Component
|
|
from forge.command import command
|
|
from forge.models.json_schema import JSONSchema
|
|
|
|
|
|
class CalculatorComponent(CommandProvider):
|
|
get_commands(self) -> Iterator[Command]:
|
|
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)
|
|
```
|
|
|
|
The agent will be able to call this command, named `multiply` with two arguments and will receive the result. The command description will be: `Multiplies two numbers.`
|
|
|
|
To learn more about commands see [🛠️ Commands](./commands.md).
|
|
|
|
## Order-dependent protocols
|
|
|
|
The order of components implementing order-dependent protocols is important.
|
|
Some components may depend on the results of components before them.
|
|
|
|
### `MessageProvider`
|
|
|
|
Yields messages that will be added to the agent's prompt. You can use either `ChatMessage.user()`: this will interpreted as a user-sent message or `ChatMessage.system()`: that will be more important.
|
|
|
|
```py
|
|
class MessageProvider(AgentComponent):
|
|
def get_messages(self) -> Iterator[ChatMessage]:
|
|
...
|
|
```
|
|
|
|
**Example** Component that provides a message to the agent's prompt.
|
|
|
|
```py
|
|
class HelloComponent(MessageProvider):
|
|
def get_messages(self) -> Iterator[ChatMessage]:
|
|
yield ChatMessage.user("Hello World!")
|
|
```
|
|
|
|
### `AfterParse`
|
|
|
|
Protocol called after the response is parsed.
|
|
|
|
```py
|
|
class AfterParse(AgentComponent):
|
|
def after_parse(self, response: ThoughtProcessOutput) -> None:
|
|
...
|
|
```
|
|
|
|
**Example** Component that logs the response after it's parsed.
|
|
|
|
```py
|
|
class LoggerComponent(AfterParse):
|
|
def after_parse(self, response: ThoughtProcessOutput) -> None:
|
|
logger.info(f"Response: {response}")
|
|
```
|
|
|
|
### `ExecutionFailure`
|
|
|
|
Protocol called when the execution of the command fails.
|
|
|
|
```py
|
|
class ExecutionFailure(AgentComponent):
|
|
@abstractmethod
|
|
def execution_failure(self, error: Exception) -> None:
|
|
...
|
|
```
|
|
|
|
**Example** Component that logs the error when the command fails.
|
|
|
|
```py
|
|
class LoggerComponent(ExecutionFailure):
|
|
def execution_failure(self, error: Exception) -> None:
|
|
logger.error(f"Command execution failed: {error}")
|
|
```
|
|
|
|
### `AfterExecute`
|
|
|
|
Protocol called after the command is successfully executed by the agent.
|
|
|
|
```py
|
|
class AfterExecute(AgentComponent):
|
|
def after_execute(self, result: ActionResult) -> None:
|
|
...
|
|
```
|
|
|
|
**Example** Component that logs the result after the command is executed.
|
|
|
|
```py
|
|
class LoggerComponent(AfterExecute):
|
|
def after_execute(self, result: ActionResult) -> None:
|
|
logger.info(f"Result: {result}")
|
|
```
|