Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug in PromptEngine and provide prompt construction strategy for most chat and generation APIs #95

Merged
merged 18 commits into from
Mar 26, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ while True:
- [Model](https://modelscope.github.io/agentscope/en/tutorial/203-model.html)
- [Service](https://modelscope.github.io/agentscope/en/tutorial/204-service.html)
- [Memory](https://modelscope.github.io/agentscope/en/tutorial/205-memory.html)
- [Prompt Engine](https://modelscope.github.io/agentscope/en/tutorial/206-prompt.html)
- [Prompt Engineering](https://modelscope.github.io/agentscope/en/tutorial/206-prompt.html)
- [Monitor](https://modelscope.github.io/agentscope/en/tutorial/207-monitor.html)
- [Distribution](https://modelscope.github.io/agentscope/en/tutorial/208-distribute.html)
- [Get Involved](https://modelscope.github.io/agentscope/en/tutorial/contribute.html)
Expand Down
296 changes: 287 additions & 9 deletions docs/sphinx_doc/en/source/tutorial/206-prompt.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,294 @@
(206-prompt-en)=

# Prompt Engine
# Prompt Engineering

**Prompt** is a crucial component in interacting with language models,
especially when seeking to generate specific types of outputs or guide the
model toward desired behaviors.
AgentScope allows developers to customize prompts according to their needs,
and provides the `PromptEngine` class to simplify the process of crafting
Prompt engineering is critical in LLM-empowered applications. However,
crafting prompts for large language models (LLMs) can be challenging,
especially with different requirements from various model APIs.

To ease the process of adapting prompt to different model APIs, AgentScope
provides a structured way to organize different data types (e.g. instruction,
hints, dialogue history) into the desired format.

Note there is no **one-size-fits-all** solution for prompt crafting.
**The goal of built-in strategies is to enable beginners to smoothly invoke
the model API, rather than achieve the best performance**.
For advanced users, we highly recommend developers to customize prompts
according to their needs and model API requirements.

## Challenges in Prompt Construction

In multi-agent applications, LLM often plays different roles in a
conversation. When using third-party chat APIs, it has the following
challenges:

1. Most third-party chat APIs are designed for chatbot scenario, and the
`role` field only supports `"user"` and `"assistant"`.

2. Some model APIs require `"user"` and `"assistant"` must speak alternatively,
and `"user"` must speak in the beginning and end of the input messages list.
Such requirements make it difficult to build a multi-agent conversation
when the agent may act as many different roles and speak continuously.

To help beginners to quickly start with AgentScope, we provide the
following built-in strategies for most chat and generation related model APIs.

## Built-in Prompt Strategies

In AgentScope, we provide built-in strategies for the following chat and
generation model APIs.

- [`OpenAIChatWrapper`](#openaichatwrapper)
- [`DashScopeChatWrapper`](#dashscopechatwrapper)
- [`OllamaChatWrapper`](#ollamachatwrapper)
- [`OllamaGenerationWrapper`](ollamagenerationwrapper)
- [`GeminiChatWrapper`](#geminiwrapper)

These strategies are implemented in the `format` functions of the model
wrapper classes.
It accepts `Msg` objects, a list of `Msg` objects, or their mixture as input.

### `OpenAIChatWrapper`

`OpenAIChatWrapper` encapsulates the OpenAI chat API, it takes a list of
dictionaries as input, where the dictionary must obey the following rules
(updated in 2024/03/22):

- Require `role` and `content` fields, and an optional `name` field.
- The `role` field must be either `"system"`, `"user"`, or `"assistant"`.

#### Prompt Strategy

In OpenAI Chat API, the `name` field enables the model to distinguish
different speakers in the conversation. Therefore, the strategy of `format`
function in `OpenAIChatWrapper` is simple:

- `Msg`: Pass a dictionary with `role`, `content`, and `name` fields directly.
- `List`: Parse each element in the list according to the above rules.

An example is shown below:

```python
from agentscope.models import OpenAIChatWrapper
from agentscope.message import Msg

model = OpenAIChatWrapper(
config_name="", # empty since we directly initialize the model wrapper
model_name="gpt-4",
)

prompt = model.format(
Msg("system", "You're a helpful assistant", role="system"), # Msg object
[ # a list of Msg objects
Msg(name="Bob", content="Hi.", role="assistant"),
Msg(name="Alice", content="Nice to meet you!", role="assistant"),
],
)
print(prompt)
```

```bash
[
{"role": "system", "name": "system", "content": "You are a helpful assistant"},
{"role": "assistant", "name": "Bob", "content": "Hi."},
{"role": "assistant", "name": "Alice", "content": "Nice to meet you!"),
]
```

### `DashScopeChatWrapper`

`DashScopeChatWrapper` encapsulates the DashScope chat API, which takes a list of messages as input. The message must obey the following rules (updated in 2024/03/22):

- Require `role` and `content` fields, and `role` must be either `"user"`
`"system"` or `"assistant"`.
- If `role` is `"system"`, this message must and can only be the first
message in the list.
- The `user` and `assistant` must speak alternatively.
- The `user` must speak in the beginning and end of the input messages list.

#### Prompt Strategy

Currently, we simply convert `Msg` objects into a dictionary with `role` and `content` fields.
We will update a more flexible strategy in the future.

An example is shown below:

```python
from agentscope.models import DashScopeChatWrapper
from agentscope.message import Msg

model = DashScopeChatWrapper(
config_name="", # empty since we directly initialize the model wrapper
model_name="qwen-max",
)

prompt = model.format(
Msg("system", "You're a helpful assistant", role="system"), # Msg object
[ # a list of Msg objects
Msg(name="Bob", content="Hi.", role="assistant"),
Msg(name="Alice", content="Nice to meet you!", role="assistant"),
],
)
print(prompt)
```

```bash
[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "assistant", "content": "Hi."},
{"role": "assistant", "content": "Nice to meet you!}
]
```

Note the output prompt doesn't follow the third requirement, which requires
`user` and `assistant` to speak alternatively.
It will be corrected within the model wrapper by a `preprocess` function,
we will merge `format` and `preprocess` function soon.

### `OllamaChatWrapper`

`OllamaChatWrapper` encapsulates the Ollama chat API, which takes a list of
messages as input. The message must obey the following rules (updated in
2024/03/22):

- Require `role` and `content` fields, and `role` must be either `"user"`,
`"system"`, or `"assistant"`.
- An optional `images` field can be added to the message

#### Prompt Strategy

Given a list of messages, we will parse each message as follows:

- `Msg`: Fill the `role` and `content` fields directly. If it has an `url`
field, which refers to an image, we will add it to the message.
- `List`: Parse each element in the list according to the above rules.

```python
from agentscope.models import OllamaChatWrapper

model = OllamaChatWrapper(
config_name="", # empty since we directly initialize the model wrapper
model_name="llama2",
)

prompt = model.format(
Msg("system", "You're a helpful assistant", role="system"), # Msg object
[ # a list of Msg objects
Msg(name="Bob", content="Hi.", role="assistant"),
Msg(name="Alice", content="Nice to meet you!", role="assistant", url="https://example.com/image.jpg"),
],
)

print(prompt)
```

```bash
[
{"role": "system", "content": "You are a helpful assistant"},
{"role": "assistant", "content": "Hi."},
{"role": "assistant", "content": "Nice to meet you!", "images": ["https://example.com/image.jpg"]},
]
```

### `OllamaGenerationWrapper`

`OllamaGenerationWrapper` encapsulates the Ollama generation API, which
takes a string prompt as input without any constraints (updated to 2024/03/22).

#### Prompt Strategy

We will ignore the `role` field and combine the prompt into a single string
of conversation.

```python
from agentscope.models import OllamaGenerationWrapper
from agentscope.message import Msg

model = OllamaGenerationWrapper(
config_name="", # empty since we directly initialize the model wrapper
model_name="llama2",
)

prompt = model.format(
Msg("system", "You're a helpful assistant", role="system"), # Msg object
[ # a list of Msg objects
Msg(name="Bob", content="Hi.", role="assistant"),
Msg(name="Alice", content="Nice to meet you!", role="assistant"),
],
)

print(prompt)
```

```bash
system: You are a helpful assistant
Bob: Hi.
Alice: Nice to meet you!
```
DavdGao marked this conversation as resolved.
Show resolved Hide resolved

### `GeminiChatWrapper`
DavdGao marked this conversation as resolved.
Show resolved Hide resolved

`GeminiChatWrapper` encapsulates the Gemini chat API, which takes a list of
messages or a string prompt as input. Similar to DashScope Chat API, if we
pass a list of messages, it must obey the following rules:

- Require `role` and `parts` fields. `role` must be either `"user"`
or `"model"`, and `parts` must be a list of strings.
- The `user` and `model` must speak alternatively.
- The `user` must speak in the beginning and end of the input messages list.

Such requirements make it difficult to build a multi-agent conversation when
an agent may act as many different roles and speak continuously.
Therefore, we decide to convert the list of messages into a user message
in our built-in `format` function.

#### Prompt Strategy

We will convert the list of messages into a string prompt as follows:

- `Msg`: Combine `name` and `content` fields into `"{name}: {content}"`
format.
- `List`: Parse each element in the list according to the above rules.

**Note** sometimes the `parts` field may contain image urls, which is not
supported in `format` function. We recommend developers to customize the
prompt according to their needs.

```python
from agentscope.models import GeminiChatWrapper
from agentscope.message import Msg

model = GeminiChatWrapper(
config_name="", # empty since we directly initialize the model wrapper
model_name="gemini-pro",
)

prompt = model.format(
Msg("system", "You're a helpful assistant", role="system"), # Msg object
[ # a list of Msg objects
Msg(name="Bob", content="Hi.", role="model"),
Msg(name="Alice", content="Nice to meet you!", role="model"),
],
)

print(prompt)
```

```bash
[
{
"role": "user",
"parts": [
"system: You are a helpful assistant\nBob: Hi.\nAlice: Nice to meet you!"
]
}
]
```

## Prompt Engine (Will be deprecated in the future)

AgentScope provides the `PromptEngine` class to simplify the process of crafting
prompts for large language models (LLMs).
This tutorial will guide you through the
use of the `PromptEngine` class, which simplifies the process of crafting
prompts for LLMs.

## About `PromptEngine` Class

Expand Down
2 changes: 1 addition & 1 deletion docs/sphinx_doc/en/source/tutorial/main.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ AgentScope is an innovative multi-agent platform designed to empower developers
- [Model](203-model-en)
- [Service](204-service-en)
- [Memory](205-memory-en)
- [Prompt Engine](206-prompt-en)
- [Prompt Engineering](206-prompt-en)
- [Monitor](207-monitor-en)
- [Distribution](208-distribute-en)

Expand Down
Loading
Loading