Skip to content

Commit

Permalink
update doc
Browse files Browse the repository at this point in the history
  • Loading branch information
liqul committed Sep 13, 2024
1 parent eb7c6a0 commit 25a9c22
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 33 deletions.
60 changes: 29 additions & 31 deletions website/blog/experience.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,6 @@ We have introduced the motivation of the `experience` module in [Experience](/do
and how to create a handcrafted experience in [Handcrafted Experience](/docs/customization/experience/handcrafted_experience).
In this blog post, we discuss more advanced topics about the experience module on experience selection.



## Static experience selection

Every role in TaskWeaver can configure its own experience directory, which can be configured
Expand All @@ -17,7 +15,7 @@ The default experience directory is `experience` in the project directory.


:::info
The role name is by default the name of the implementation file of the role unless
The role name is by default the name of the implementation file (without the extension) of the role unless
you have specified the role name by calling `_set_name` in the implementation file.
:::

Expand Down Expand Up @@ -71,6 +69,10 @@ planner_experience
When we can identify the task type based on the task ID, we can set the experience subdirectory.
This looks straightforward, but how can we set the experience subdirectory in TaskWeaver?
As we need to do this in a dynamic way, the only way is to set the experience subdirectory in a [role](/docs/concepts/role).

TaskWeaver recently introduced the concept of shared memory as discussed in [Shared Memory](/docs/memory).
Shared memory allows a role to share information with other roles, and in this case, we can use shared memory to set the experience subdirectory.

We can add a new role called `TaskTypeIdentifier` to identify the task type based on the task ID.
The key part of the `reply` function in `TaskTypeIdentifier` is shown below:

Expand All @@ -79,49 +81,45 @@ def reply(self, memory: Memory, **kwargs: ...) -> Post:
# ...
# get the task type from the last post message
task_type = get_task_type(last_post.message)
# issue a signal to set the experience subdirectory
# create an attachment
post_proxy.update_attachment(
type=AttachmentType.signal,
message=f"_signal_exp_sub_path:{task_type}",
type=AttachmentType.shared_memory_entry,
message="Add experience sub path",
extra=SharedMemoryEntry.create(
type="experience_sub_path",
by=self.alias,
scope="conversation",
scope_id=memory.conversation.id,
content="task_type_1",
aggregation_keys=("type", ),
),
)

return post_proxy.end()
```

In the `reply` method, we first obtain the query from the last round.
Then we identify the task type based on the query content.
The interesting part is that we set the experience subdirectory in the attachment of the response.
We set the attachment type to `signal` and the message to `_signal_exp_sub_path:{task_type}`.

The `signal` attachment type is a special type in TaskWeaver, which is used to send signals to other roles.
Its content is a string with format `key:value`, where `key` is an attachment type and `value` is the content of the attachment.
In this case, we send a signal to all roles to set the experience subdirectory to the value of `task_type`.
This is done by broadcasting an attachment with type `_signal_exp_sub_path` and its value, which is the task type,
to all the roles in TaskWeaver. Every role can decide whether to use the signal or not.

A role responds to the signal by setting the experience subdirectory to the value in the signal.
The key part of the `Planner` role implementation is shown below:
In a role that needs to set the experience subdirectory, we can get the experience subdirectory from the shared memory.

```python
# obtain the experience subdirectory from the attachment of the last post
exp_sub_path = last_post.get_attachment(AttachmentType._signal_exp_sub_path)
if exp_sub_path:
self.tracing.set_span_attribute("exp_sub_path", exp_sub_path[0])
exp_sub_path = exp_sub_path[0]
exp_sub_paths = memory.get_shared_memory_entry(
entry_type="experience_sub_path",
entry_scopes=["conversation"],
entry_scope_ids=[memory.conversation.id],
)

if exp_sub_paths:
exp_sub_path = exp_sub_paths[0].content
else:
exp_sub_path = ""
selected_experiences = self.load_experience(query=query, sub_path=exp_sub_path)
```
Other roles that are not responsible for setting the experience subdirectory can ignore the attachment.
In this way, we can set the experience subdirectory dynamically based on the task type.

The signal is maintained at the session level, which means that the signal is valid for the current session.
The value of the signal can be changed by sending another signal with the same attachment type.
Note that after the signal is set, all roles will keep receiving the signal until the session ends.
So, it is each role's responsibility to implement the logic to handle duplicate signals.
:::tip
This is the current experimental feature in TaskWeaver which is subject to change.
:::

## Conclusion

In this blog post, we have discussed how to select experiences in TaskWeaver.
We have static experience selection by configuring the experience directory for each role.
To enable dynamic experience selection, we have introduced the concept of experience subdirectory and signal attachment.
To enable dynamic experience selection, we have introduced the concept of shared memory to set the experience subdirectory.
4 changes: 4 additions & 0 deletions website/docs/code_execution.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,10 @@ As a result, in the `local` mode, if the user has malicious intent, the user cou
instruct TaskWeaver to execute harmful code on the host machine. In addition, the LLM could also generate
harmful code, leading to potential security risks.

:::danger
Please be cautious when using the `local` mode, especially when the usage of the agent is open to untrusted users.
:::



## How to Configure the Code Execution Mode
Expand Down
114 changes: 114 additions & 0 deletions website/docs/memory.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
# Memory

The primary purpose of the `memory` module is to store the information that is required to maintain the context of the conversation.
You can find the implementation code in `taskweaver/memory/memory.py`.
We have introduced various concepts such as `Round` and `Post` in the [concepts](concepts/) section,
which are the building blocks of the Memory module.

There are two kinds of information that are stored in the memory:

1. **Conversation History**: This includes the conversation that has happened so far between the user and various roles in TaskWeaver.
2. **Shared Memory**: This includes the information that is purposefully shared between the roles in TaskWeaver.

Let's briefly discuss the two types of information.

## Role-wise Conversation History

A TaskWeaver agent is composed of one or more roles. Each [role](concepts/role.md) have its own conversation history.
In TaskWeaver, we orchestrate the roles in a star topology, where the Planner is at the center and the roles are at the periphery.
The User only interacts with the Planner, and the Planner interacts with the roles, making planning and instructing other roles to carry out the tasks,
as shown in the figure below.

```mermaid
graph TD
User --> Planner
Planner --> Role1
Role1 --> Planner
Planner --> Role2
Role2 --> Planner
Planner --> Role3
Role3 --> Planner
Planner --> User
```

Though this fixed orchestration is a limitation, it reserves the independence of the roles. Each role does not need to know about the other roles, even their existence. For any peripheral role, the Planner is the only point of contact, i.e., its real "User",
and it only focuses on its own expertise. It is the Planner's responsibility to leverage multiple roles
for complex tasks and orchestrate them to achieve the goal.

The conversation history of each role is stored in the memory. When a role needs to prepare a response, it can refer to the conversation history
to understand the context of the conversation. Specifically, this typically is the process of preparing a prompt for the LLM, containing all
the previous chat rounds and the current request. A role only cares about the Posts sent or received by itself.

## Shared Memory

While we want to maintain the independence of the roles, there are cases where the roles need to share information.
One common case is about information sharing between two peripheral roles. For example, the Planner may ask the Code Interpreter to generate code
based on the guide provided by a Data Scientist role. In this case, the Planner needs to share the guide with the Code Interpreter.
In theory, this can be done by the Planner repeating the guide to the Code Interpreter, shown in the figure below.

```mermaid
graph TD
User --1--> Planner
Planner --2--> DataScientist
DataScientist --3--> Planner
Planner --4--> CodeInterpreter
CodeInterpreter --5--> Planner
Planner --6--> User
```
However, we found that the Planner can fail to repeat the guide accurately, leading to miscommunication.

Another use case is a role needing to store some control state that is shared among all roles. For example, the agent needs to handle multiple types
of tasks. The "type" of the current user request is only determined by one role (e.g., namely `TypeDeterminer`), but all other roles need to know the type to prepare the response.
In this case, the role that determines the type can store the type in the shared memory, and other roles can refer to the shared memory to get the type.

```mermaid
graph TD
User --1--> Planner
Planner --2--> TypeDeterminer
TypeDeterminer --3--> Planner
TypeDeterminer --3--> SharedMemory
SharedMemory --4--> Worker1
SharedMemory --6--> Worker2
Planner --4--> Worker1
Worker1 --5--> Planner
Planner --6--> Worker2
Worker2 --7--> Planner
Planner --8--> User
```

:::tip
The number in the arrows indicates the order of the information flow.
:::

For the aforementioned reasons, we introduced the concept of Shared Memory. The Shared Memory is a special [Attachment](concepts/attachment.md) that is appended to the
post of the role who wants to share information.
The attachment has a instance of SharedMemoryEntry in the `extra` field. The SharedMemoryEntry has the following fields:

```python
class SharedMemoryEntry:
type: Literal[...] # The type of the shared memory entry
by: str # The role that shared the information
content: str # The content of the shared information
scope: Literal["round", "conversation"] # The scope of the shared information
scope_id: str # The id of the scope, such as round_id or conversation_id
aggregation_keys: Tuple[Literal["by", "type", "scope", "scope_id", "id"], ...] # The keys to aggregate the shared memory entries
id: str # The id of the shared memory entry
```

One question may be why we do not store the shared information in a separate data structure, instead of the Attachment in Posts.
The reason is that, if a Round fails, we may need to remove the shared information within that Round as well. By storing the shared information in the Attachment,
as a part of the Post, we can easily filter out the shared information based on the Round status.
This is similar with designing the logging system of database operations in a transaction.

The consumer of the shared information can use `type`, `scope`, and `scope_id` to filter out the shared information that is relevant to it.
The `by` field is not suggested to be used for filtering, as it requires the consumer to know the role that shared the information.
The two fields `scope` and `scope_id` are used to determine the scope of the shared information. The `scope` can be either `round` or `conversation`.
For example, if one piece of information is only effective for the current round, the `scope` is `round` and the `scope_id` is the current `round_id`.
Similarly, if one piece of information is effective for the whole conversation, the `scope` is `conversation` and the `scope_id` is the `conversation_id`.

The `aggregation_keys` field is used to aggregate the shared information when there are multiple shared information entries with the same `type`, `scope`, and `scope_id`.
If more than one shared information entries have the same aggregation key, only the latest one along the conversation history is kept.
We allow the generator role to define the aggregation keys. We include a unique `id` field in the SharedMemoryEntry to distinguish the shared information entries.
So, if the `aggregation_keys = ("id",)`, the shared information entries are not aggregated.

A reference implementation of the Shared Memory is provided in the `taskweaver/planner/planner.py`, where the Planner role shares the details of the plan with other roles.
7 changes: 6 additions & 1 deletion website/docs/usage/docker.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
# All-in-One Docker Image

In this document, we will show you how to run TaskWeaver using the All-in-One Docker Image.
Please note that the All-in-One Docker Image is for development and testing purposes only.

:::danger
Please note that the All-in-One Docker Image is for development and testing purposes only. It is running in
the [local](../code_execution.md) mode, which means that the execution of the code snippets is done in the same container.
Malicious code can be executed in the container, so please be cautious when running the All-in-One Docker Image.
:::

## Prerequisites
You need to have Docker installed on your machine.
Expand Down
7 changes: 6 additions & 1 deletion website/docs/usage/webui.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
# Web UI

Please note that this Web UI is for development and testing purposes only.
:::warning
Please note that this Web UI is a playground for development and testing purposes only.
Be cautious when running the Web UI, as anyone can access it if the port is open to the public.
If you want to deploy a Web UI for production, you need to address security concerns, such as authentication and authorization,
making sure the server is secure.
:::

Follow the instruction in [Quick Start](../quickstart.md) to clone the repository and fill in the necessary configurations.

Expand Down
1 change: 1 addition & 0 deletions website/sidebars.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ const sidebars = {
'overview',
'quickstart',
'code_execution',
'memory',
'FAQ',
{
type: 'category',
Expand Down

0 comments on commit 25a9c22

Please sign in to comment.