Skip to content

Commit 6201735

Browse files
committed
fix actions docs and reference in external actions doc
1 parent 9bb3480 commit 6201735

File tree

2 files changed

+131
-35
lines changed

2 files changed

+131
-35
lines changed

docs/open-source/action-agents.mdx

+119-21
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,11 @@ description: 'Empowering agents to take actions during conversations.'
1010
Actions refer to specific tools an agent can execute during the conversation. These actions can encompass various activities like writing an email,
1111
scheduling an appointment, and so forth. They are implemented as classes derived from the `BaseAction` class.
1212

13-
The `BaseAction` class is defined as follows:
13+
The `BaseAction` class has the following key structure:
1414

1515
```python
1616
class BaseAction(Generic[ActionOutputType]):
17-
def run(self, params: str) -> ActionOutputType:
17+
async def run(self, action_input: ActionInput[ParametersType]) -> ActionOutput[ResponseType]:
1818
raise NotImplementedError
1919
```
2020

@@ -31,7 +31,7 @@ The agent configuration lists the actions available to the agent:
3131

3232
```python
3333
class AgentConfig(AgentConfig, type=AgentType.Base.value):
34-
actions: List[ActionType]
34+
actions: Optional[List[ActionConfig]]
3535
# ...
3636
```
3737

@@ -51,24 +51,76 @@ The flow of actions is as follows:
5151
5. The agent then consumes the `ActionResultAgentInput` from the queue in its process method. This
5252
result is added to the transcript of the conversation, and can influence the behavior of the agent in subsequent interactions.
5353

54-
### Implementing your own action: Nylas email example
54+
## Implementing your own action: Nylas email example
5555

5656
In this section, we provide an example of an action, `NylasSendEmail`, which extends the `BaseAction` class. It implements the run method to send an email using the Nylas API.
57+
We will also show how to add this action to an agent and use it in a conversation.
5758

59+
### Creating the Action
5860
```python
59-
class NylasSendEmail(BaseAction[NylasSendEmailActionOutput]):
60-
def run(self, params: str) -> NylasSendEmailActionOutput:
61-
"""Sends an email using Nylas API.
62-
The input to this action is a pipe separated list of the recipient email, email body, optional subject. But always include
63-
the pipe character even if the subject or message IDs are not included and just leave it blank.
64-
The subject should only be included if it is a new email thread.
65-
If there is no message id, the email will be sent as a new email. Otherwise, it will be sent as a reply to the given message. Make sure to include the previous message_id
66-
if you are replying to an email.
67-
68-
For example, `[email protected]|Hello, this is the email body.|this is the subject` would send an email to [email protected] with the provided body and subject.
69-
"""
70-
recipient_email, email_body, email_subject = params.split("|")
61+
import os
62+
from typing import Optional, Type
63+
64+
from pydantic import Field
65+
from pydantic.v1 import BaseModel
66+
67+
from vocode.streaming.action.base_action import BaseAction
68+
from vocode.streaming.models.actions import ActionConfig as VocodeActionConfig
69+
from vocode.streaming.models.actions import ActionInput, ActionOutput
70+
71+
72+
_NYLAS_ACTION_DESCRIPTION = """Sends an email using Nylas API.
73+
The input to this action is the recipient emails, email body, optional subject.
74+
The subject should only be included if it is a new email thread.
75+
If there is no message id, the email will be sent as a new email. Otherwise, it will be sent as a reply to the given message. Make sure to include the previous message_id
76+
if you are replying to an email.
77+
"""
78+
79+
80+
class NylasSendEmailParameters(BaseModel):
81+
email: str = Field(..., description="The email address to send the email to.")
82+
subject: Optional[str] = Field(None, description="The subject of the email.")
83+
body: str = Field(..., description="The body of the email.")
84+
85+
86+
class NylasSendEmailResponse(BaseModel):
87+
success: bool
88+
89+
90+
class NylasSendEmailVocodeActionConfig(
91+
VocodeActionConfig, type="action_nylas_send_email" # type: ignore
92+
):
93+
pass
94+
95+
class NylasSendEmail(
96+
BaseAction[
97+
NylasSendEmailVocodeActionConfig,
98+
NylasSendEmailParameters,
99+
NylasSendEmailResponse,
100+
]
101+
):
102+
description: str = _NYLAS_ACTION_DESCRIPTION
103+
parameters_type: Type[NylasSendEmailParameters] = NylasSendEmailParameters
104+
response_type: Type[NylasSendEmailResponse] = NylasSendEmailResponse
105+
106+
def __init__(
107+
self,
108+
action_config: NylasSendEmailVocodeActionConfig,
109+
):
110+
super().__init__(
111+
action_config,
112+
quiet=True,
113+
is_interruptible=True,
114+
)
71115

116+
async def _end_of_run_hook(self) -> None:
117+
"""This method is called at the end of the run method. It is optional but intended to be
118+
overridden if needed."""
119+
print("Successfully sent email!")
120+
121+
async def run(
122+
self, action_input: ActionInput[NylasSendEmailParameters]
123+
) -> ActionOutput[NylasSendEmailResponse]:
72124
from nylas import APIClient
73125

74126
# Initialize the Nylas client
@@ -80,17 +132,63 @@ class NylasSendEmail(BaseAction[NylasSendEmailActionOutput]):
80132

81133
# Create the email draft
82134
draft = nylas.drafts.create()
83-
draft.body = email_body
135+
draft.body = action_input.params.body
84136

85137
draft.subject = (
86-
email_subject.strip() if email_subject.strip() else "Email from Vocode"
138+
action_input.params.subject.strip() if action_input.params.subject else "Email from Vocode"
87139
)
88-
draft.to = [{"email": recipient_email.strip()}]
140+
draft.to = [{"email": action_input.params.email.strip()}]
89141

90142
# Send the email
91143
draft.send()
92144

93-
return NylasSendEmailActionOutput(response=json.dumps({"success": True}))
145+
await self._end_of_run_hook()
146+
return ActionOutput(
147+
action_type=action_input.action_config.type,
148+
response=NylasSendEmailResponse(success=True),
149+
)
150+
151+
```
152+
We define a structured set of parameters that the LLM will fill, a response structure that the agent can ingest and use as context for the rest of the conversation, and an action
153+
config that crucially contains the `action_type` (but doesn't have any other specific parameters). We also add an `_end_of_run_hook()` that we customized to log that the action successfully completed.
154+
155+
156+
### Making a custom `ActionFactory`
157+
To use our new action with an agent in a conversation, we will need to create an `ActionFactory` that can produce instances your custom action.
158+
159+
160+
We will store the code above in `nylas_send_email.py` and create a factory that can create this action for an agent:
161+
162+
```python
163+
from typing import Dict, Sequence, Type
164+
165+
from vocode.streaming.action.abstract_factory import AbstractActionFactory
166+
from vocode.streaming.action.base_action import BaseAction
167+
from vocode.streaming.action.nylas_send_email import NylasSendEmail
168+
from vocode.streaming.models.actions import ActionConfig
169+
170+
class MyCustomActionFactory(AbstractActionFactory):
171+
def create_action(self, action_config: ActionConfig):
172+
if action_config.type == "action_nylas_send_email":
173+
return NylasSendEmail(action_config)
174+
else:
175+
raise Exception("Action type not supported by Agent config.")
176+
```
177+
178+
### Using your action and action factory in an agent
179+
180+
Now you can use your action in any conversation by adding the action config to any agent config and passing in your action factory. For example, you can create a ChatGPT agent config:
181+
182+
```python
183+
action_config = ChatGPTAgentConfig(
184+
...
185+
actions = [
186+
NylasSendEmailVocodeActionConfig(
187+
type = "action_nylas_send_email"
188+
)
189+
]
190+
)
94191
```
95192

96-
See [Agent Factory](/open-source/agent-factory) for more information on how to register your action with the agent factory.
193+
Now, you have a config for an agent that uses your new action! When you pass in your new action factory [created above](#making-a-custom-actionfactory), any agent factory can generate this agent for conversations.
194+
See [Agent Factory](/open-source/agent-factory) for more information on how to use your action factory in an agent factory, as well as how to plug it into a telephony server.

docs/open-source/external-action.mdx

+12-14
Original file line numberDiff line numberDiff line change
@@ -221,9 +221,9 @@ fastapi==0.111.*
221221

222222
</CodeGroup>
223223

224-
## Meeting Assistant Example:
224+
## Meeting Assistant Example
225225

226-
This is an example of how to configure a Meeting Assistant Action which will attempt to book a meeting for 30 minutes or an hour at any time ending in a zero (ie 10:30am is okay but 10:35am is not)
226+
This is an example of how to create an action config which will attempt to book a meeting for 30 minutes or an hour at any time ending in a zero (ie 10:30am is okay but 10:35am is not)
227227

228228
```python Python
229229
import json
@@ -249,17 +249,15 @@ ACTION_INPUT_SCHEMA = {
249249
},
250250
}
251251

252-
action_config = {
253-
"name": "Meeting_Booking_Assistant",
254-
"description": "Book a meeting for a 30 minute or 1 hour call.",
255-
"url": "http://example.com/booking",
256-
"speak_on_send": True,
257-
"speak_on_receive": True,
258-
"input_schema": json.dumps(ACTION_INPUT_SCHEMA),
259-
"signature_secret": base64.b64encode(os.urandom(32)).decode(),
260-
}
261-
262-
action = ExecuteExternalAction(
263-
action_config=ExecuteExternalActionVocodeActionConfig(**action_config),
252+
action_config = ExecuteExternalActionVocodeActionConfig(
253+
name = "Meeting_Booking_Assistant",
254+
description = "Book a meeting for a 30 minute or 1 hour call.",
255+
url = "http://example.com/booking",
256+
speak_on_send = True,
257+
speak_on_receive = True,
258+
input_schema = json.dumps(ACTION_INPUT_SCHEMA),
259+
signature_secret = base64.b64encode(os.urandom(32)).decode(),
264260
)
265261
```
262+
See [Action Agents](/open-source/action-agents#using-your-action-and-action-factory-in-an-agent) for an example of how to use this action config with an agent in a conversation.
263+
Note that you do not need to create a custom action factory, since `DefaultActionFactory` already supports external actions.

0 commit comments

Comments
 (0)