This commit is contained in:
CakeCN
2024-12-03 16:21:19 +08:00
commit 4198ca63b1
975 changed files with 333413 additions and 0 deletions

View File

@@ -0,0 +1,4 @@
.language-selector a {
color: white;
width: 20px;
}

View File

@@ -0,0 +1,5 @@
<!-- language_selector.html -->
<div class="language-selector">
<a href="{{ pathto('../en/' + pagename) }}">English</a></li> |
<a href="{{ pathto('../zh_CN/' + pagename) }}">中文</a></li>
</div>

View File

@@ -0,0 +1,3 @@
<!-- layout.html -->
{% extends "!layout.html" %} {% block sidebartitle %} {{ super() }} {% include
"language_selector.html" %} {% endblock %}

View File

@@ -0,0 +1,93 @@
# -*- coding: utf-8 -*-
# Configuration file for the Sphinx documentation builder.
#
# This file only contains a selection of the most common options. For a full
# list see the documentation:
# https://www.sphinx-doc.org/en/master/usage/configuration.html
# -- Path setup --------------------------------------------------------------
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
# documentation root, use os.path.abspath to make it absolute, like shown here.
#
# import os
# import sys
# sys.path.insert(0, os.path.abspath("../../../src/agentscope"))
# -- Project information -----------------------------------------------------
language = "en"
project = "AgentScope"
copyright = "2024, Alibaba Tongyi Lab"
author = "SysML team of Alibaba Tongyi Lab"
# -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
"sphinx.ext.autodoc",
"sphinx.ext.autosummary",
"sphinx.ext.viewcode",
"sphinx.ext.napoleon",
"sphinxcontrib.mermaid",
"myst_parser",
"sphinx.ext.autosectionlabel",
]
# Prefix document path to section labels, otherwise autogenerated labels would
# look like 'heading' rather than 'path/to/file:heading'
autosectionlabel_prefix_document = True
autosummary_generate = True
autosummary_ignore_module_all = False
autodoc_member_order = "bysource"
autodoc_default_options = {
"members": True,
"special-members": "__init__",
}
# Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"]
# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
# This pattern also affects html_static_path and html_extra_path.
exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"]
autodoc_default_options = {
"members": True,
"special-members": "__init__",
}
# -- Options for HTML output -------------------------------------------------
# The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes.
#
html_theme = "sphinx_rtd_theme"
# Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"]
html_theme_options = {
"navigation_depth": 2,
}
source_suffix = {
".rst": "restructuredtext",
".md": "markdown",
}
html_css_files = [
"custom.css",
]

View File

@@ -0,0 +1,61 @@
.. AgentScope documentation master file, created by
sphinx-quickstart on Fri Jan 5 17:53:54 2024.
You can adapt this file completely to your liking, but it should at least
contain the root `toctree` directive.
:github_url: https://github.com/modelscope/agentscope
AgentScope Documentation
======================================
.. include:: tutorial/main.md
:parser: myst_parser.sphinx_
.. toctree::
:maxdepth: 1
:glob:
:hidden:
:caption: AgentScope Tutorial
tutorial/101-agentscope.md
tutorial/102-installation.md
tutorial/103-example.md
tutorial/203-model.md
tutorial/206-prompt.md
tutorial/201-agent.md
tutorial/205-memory.md
tutorial/203-parser.md
tutorial/209-prompt_opt.md
tutorial/204-service.md
tutorial/202-pipeline.md
tutorial/208-distribute.md
tutorial/209-gui.md
tutorial/210-rag.md
tutorial/105-logging.md
tutorial/207-monitor.md
tutorial/104-usecase.md
tutorial/contribute.rst
.. toctree::
:maxdepth: 1
:glob:
:caption: AgentScope API Reference
agentscope
agentscope.message
agentscope.models
agentscope.agents
agentscope.memory
agentscope.parsers
agentscope.exception
agentscope.pipelines
agentscope.service
agentscope.rpc
agentscope.server
agentscope.web
agentscope.prompt
agentscope.utils

View File

@@ -0,0 +1,116 @@
(101-agentscope-en)=
# About AgentScope
In this tutorial, we will provide an overview of AgentScope by answering
several questions, including what's AgentScope, what can AgentScope provide,
and why we should choose AgentScope. Let's get started!
## What is AgentScope?
AgentScope is a developer-centric multi-agent platform, which enables
developers to build their LLM-empowered multi-agent applications with less
effort.
With the advance of large language models, developers are able to build
diverse applications.
In order to connect LLMs to data and services and solve complex tasks,
AgentScope provides a series of development tools and components for ease of
development.
It features
- **usability**,
- **robustness**,
- **the support of multi-modal data**,
- **distributed deployment**.
## Key Concepts
### Message
Message is a carrier of information (e.g. instructions, multi-modal
data, and dialogue). In AgentScope, message is a Python dict subclass
with `name` and `content` as necessary fields, and `url` as an optional
field referring to additional resources.
### Agent
Agent is an autonomous entity capable of interacting with environment and
agents, and taking actions to change the environment. In AgentScope, an
agent takes message as input and generates corresponding response message.
### Service
Service refers to the functional APIs that enable agents to perform
specific tasks. In AgentScope, services are categorized into model API
services, which are channels to use the LLMs, and general API services,
which provide a variety of tool functions.
### Workflow
Workflow represents ordered sequences of agent executions and message
exchanges between agents, analogous to computational graphs in TensorFlow,
but with the flexibility to accommodate non-DAG structures.
## Why AgentScope?
**Exceptional usability for developers.**
AgentScope provides high usability for developers with flexible syntactic
sugars, ready-to-use components, and pre-built examples.
**Robust fault tolerance for diverse models and APIs.**
AgentScope ensures robust fault tolerance for diverse models, APIs, and
allows developers to build customized fault-tolerant strategies.
**Extensive compatibility for multi-modal application.**
AgentScope supports multi-modal data (e.g., files, images, audio and videos)
in both dialog presentation, message transmission and data storage.
**Optimized efficiency for distributed multi-agent operations.** AgentScope
introduces an actor-based distributed mechanism that enables centralized
programming of complex distributed workflows, and automatic parallel
optimization.
## How is AgentScope designed?
The architecture of AgentScope comprises three hierarchical layers. The
layers provide supports for multi-agent applications from different levels,
including elementary and advanced functionalities of a single agent
(**utility layer**), resources and runtime management (**manager and wrapper
layer**), and agent-level to workflow-level programming interfaces (**agent
layer**). AgentScope introduces intuitive abstractions designed to fulfill
the diverse functionalities inherent to each layer and simplify the
complicated interlayer dependencies when building multi-agent systems.
Furthermore, we offer programming interfaces and default mechanisms to
strengthen the resilience of multi-agent systems against faults within
different layers.
## AgentScope Code Structure
```bash
AgentScope
├── src
│ ├── agentscope
| ├── agents # Core components and implementations pertaining to agents.
| ├── memory # Structures for agent memory.
| ├── models # Interfaces for integrating diverse model APIs.
| ├── pipelines # Fundamental components and implementations for running pipelines.
| ├── rpc # Rpc module for agent distributed deployment.
| ├── service # Services offering functions independent of memory and state.
| | ├── web # WebUI used to show dialogs.
| ├── utils # Auxiliary utilities and helper functions.
| ├── message.py # Definitions and implementations of messaging between agents.
| ├── prompt.py # Prompt engineering module for model input.
| ├── ... ..
| ├── ... ..
├── scripts # Scripts for launching local Model API
├── examples # Pre-built examples of different applications.
├── docs # Documentation tool for API reference.
├── tests # Unittest modules for continuous integration.
├── LICENSE # The official licensing agreement for AgentScope usage.
└── setup.py # Setup script for installing.
├── ... ..
└── ... ..
```
[[Return to the top]](#101-agentscope)

View File

@@ -0,0 +1,68 @@
(102-installation-en)=
# Installation
To install AgentScope, you need to have Python 3.9 or higher installed. We recommend setting up a new virtual environment specifically for AgentScope:
## Create a Virtual Environment
### Using Conda
If you're using Conda as your package and environment management tool, you can create a new virtual environment with Python 3.9 using the following commands:
```bash
# Create a new virtual environment named 'agentscope' with Python 3.9
conda create -n agentscope python=3.9
# Activate the virtual environment
conda activate agentscope
```
### Using Virtualenv
Alternatively, if you prefer `virtualenv`, you can install it first (if it's not already installed) and then create a new virtual environment as shown:
```bash
# Install virtualenv if it is not already installed
pip install virtualenv
# Create a new virtual environment named 'agentscope' with Python 3.9
virtualenv agentscope --python=python3.9
# Activate the virtual environment
source agentscope/bin/activate # On Windows use `agentscope\Scripts\activate`
```
## Installing AgentScope
### Install with Pip
If you prefer to install AgentScope from Pypi, you can do so easily using `pip`:
```bash
# For centralized multi-agent applications
pip install agentscope --pre
# For distributed multi-agent applications
pip install agentscope[distribute] --pre # On Mac use `pip install agentscope\[distribute\] --pre`
```
### Install from Source
For users who prefer to install AgentScope directly from the source code, follow these steps to clone the repository and install the platform in editable mode:
**_Note: This project is under active development, it's recommended to install AgentScope from source._**
```bash
# Pull the source code from Github
git clone https://github.com/modelscope/agentscope.git
cd agentscope
# For centralized multi-agent applications
pip install -e .
# For distributed multi-agent applications
pip install -e .[distribute] # On Mac use `pip install -e .\[distribute\]`
```
**Note**: The `[distribute]` option installs additional dependencies required for distributed applications. Remember to activate your virtual environment before running these commands.
[[Return to the top]](#102-installation-en)

View File

@@ -0,0 +1,108 @@
(103-start-en)=
# Quick Start
AgentScope is designed with a flexible communication mechanism.
In this tutorial, we will introduce the basic usage of AgentScope via a
simple standalone conversation between two agents (e.g. user and assistant
agents).
## Step1: Prepare Model
AgentScope decouples the deployment and invocation of models to better build multi-agent applications.
In terms of model deployment, users can use third-party model services such
as OpenAI API, Google Gemini API, HuggingFace/ModelScope Inference API, or
quickly deploy local open-source model services through the [scripts](https://github.com/modelscope/agentscope/blob/main/scripts/README.md) in
the repository.
While for model invocation, users should prepare a model configuration to specify the model service. Taking OpenAI Chat API as an example, the model configuration is like this:
```python
model_config = {
"config_name": "{config_name}", # A unique name for the model config.
"model_type": "openai_chat", # Choose from "openai_chat", "openai_dall_e", or "openai_embedding".
"model_name": "{model_name}", # The model identifier used in the OpenAI API, such as "gpt-3.5-turbo", "gpt-4", or "text-embedding-ada-002".
"api_key": "xxx", # Your OpenAI API key. If unset, the environment variable OPENAI_API_KEY is used.
"organization": "xxx", # Your OpenAI organization ID. If unset, the environment variable OPENAI_ORGANIZATION is used.
}
```
More details about model invocation, deployment and open-source models please refer to [Model](203-model-en) section.
After preparing the model configuration, you can register your configuration by calling the `init` method of AgentScope. Additionally, you can load multiple model configurations at once.
```python
import agentscope
# init once by passing a list of config dict
openai_cfg_dict = {
# ...
}
modelscope_cfg_dict = {
# ...
}
agentscope.init(model_configs=[openai_cfg_dict, modelscope_cfg_dict])
```
## Step2: Create Agents
Creating agents is straightforward in AgentScope. After initializing AgentScope with your model configurations (Step 1 above), you can then define each agent with its corresponding role and specific model.
```python
import agentscope
from agentscope.agents import DialogAgent, UserAgent
# read model configs
agentscope.init(model_configs="./openai_model_configs.json")
# Create a dialog agent and a user agent
dialogAgent = DialogAgent(name="assistant", model_config_name="gpt-4", sys_prompt="You are a helpful ai assistant")
userAgent = UserAgent()
```
**NOTE**: Please refer to [Customizing Your Own Agent](201-agent-en) for all available agents.
## Step3: Agent Conversation
"Message" is the primary means of communication between agents in AgentScope. They are Python dictionaries comprising essential fields like the actual `content` of this message and the sender's `name`. Optionally, a message can include a `url` to either a local file (image, video or audio) or website.
```python
from agentscope.message import Msg
# Example of a simple text message from Alice
message_from_alice = Msg("Alice", "Hi!")
# Example of a message from Bob with an attached image
message_from_bob = Msg("Bob", "What about this picture I took?", url="/path/to/picture.jpg")
```
To start a conversation between two agents, such as `dialog_agent` and `user_agent`, you can use the following loop. The conversation continues until the user inputs `"exit"` which terminates the interaction.
```python
x = None
while True:
x = dialogAgent(x)
x = userAgent(x)
# Terminate the conversation if the user types "exit"
if x.content == "exit":
print("Exiting the conversation.")
break
```
For a more advanced approach, AgentScope offers the option of using pipelines to manage the flow of messages between agents. The `sequentialpipeline` stands for sequential speech, where each agent receive message from last agent and generate its response accordingly.
```python
from agentscope.pipelines.functional import sequentialpipeline
# Execute the conversation loop within a pipeline structure
x = None
while x is None or x.content != "exit":
x = sequentialpipeline([dialog_agent, user_agent])
```
For more details about how to utilize pipelines for complex agent interactions, please refer to [Pipeline and MsgHub](202-pipeline-en).
[[Return to the top]](#103-start-en)

View File

@@ -0,0 +1,302 @@
(104-usecase-en)=
# Example: Werewolf Game
<img src="https://img.alicdn.com/imgextra/i3/O1CN01dFpOh82643mygUh2Z_!!6000000007607-2-tps-1024-1024.png" alt="img" style="zoom:25%;" />
**Werewolf** is a well-known social-deduction game, that involves an imaginary village where a few villagers are secretly werewolves, and the objective is to identify who they are before they eliminate all other players. It's a good use case to demonstrate the interaction between multiple autonomous agents, each with its own objectives and the need for communication.
Let the adventure begin to unlock the potential of multi-agent applications with AgentScope!
## Getting Started
Firstly, ensure that you have installed and configured AgentScope properly. Besides, we will involve the basic concepts of `Model API`, `Agent`, `Msg`, and `Pipeline,` as described in [Tutorial-Concept](101-agentscope.md).
**Note**: all the configurations and code for this tutorial can be found in `examples/game_werewolf`.
### Step 1: Prepare Model API and Set Model Configs
As we discussed in the last tutorial, you need to prepare your model configurations into a JSON file for standard OpenAI chat API, FastChat, and vllm. More details and advanced usages such as configuring local models with POST API are presented in [Tutorial-Model-API](203-model.md).
```json
[
{
"config_name": "gpt-4-temperature-0.0",
"model_type": "openai_chat",
"model_name": "gpt-4",
"api_key": "xxx",
"organization": "xxx",
"generate_args": {
"temperature": 0.0
}
}
]
```
### Step 2: Define the Roles of Each Agent
In the Werewolf game, agents assume a variety of roles, each endowed with distinctive abilities and objectives. Below, we will outline the agent classes corresponding to each role:
- Villager: Ordinary townsfolk trying to survive.
- Werewolf: Predators in disguise, aiming to outlast the villagers.
- Seer: A villager with the power to see the true nature of one player each night.
- Witch: A villager who can save or poison a player each night.
To implement your own agent, you need to inherit `AgentBase` and implement the `reply` function, which is executed when an agent instance is called via `agent1(x)`.
```python
from agentscope.agents import AgentBase
from agentscope.message import Msg
from typing import Optional, Union, Sequence
class MyAgent(AgentBase):
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
# Do something here
...
return x
```
AgentScope provides several out-of-the-box Agents implements and organizes them as an *Agent Pool*. In this application, we use a built-in agent, `DictDialogAgent`. Here we give an example configuration of `DictDialogAgent` for a player assigned as the role of a werewolf:
```json
{
"class": "DictDialogAgent",
"args": {
"name": "Player1",
"sys_prompt": "Act as a player in a werewolf game. You are Player1 and\nthere are totally 6 players, named Player1, Player2, Player3, Player4, Player5 and Player6.\n\nPLAYER ROLES:\nIn werewolf game, players are divided into two werewolves, two villagers, one seer, and one witch. Note only werewolves know who are their teammates.\nWerewolves: They know their teammates' identities and attempt to eliminate a villager each night while trying to remain undetected.\nVillagers: They do not know who the werewolves are and must work together during the day to deduce who the werewolves might be and vote to eliminate them.\nSeer: A villager with the ability to learn the true identity of one player each night. This role is crucial for the villagers to gain information.\nWitch: A character who has a one-time ability to save a player from being eliminated at night (sometimes this is a potion of life) and a one-time ability to eliminate a player at night (a potion of death).\n\nGAME RULE:\nThe game consists of two phases: night phase and day phase. The two phases are repeated until werewolf or villager wins the game.\n1. Night Phase: During the night, the werewolves discuss and vote for a player to eliminate. Special roles also perform their actions at this time (e.g., the Seer chooses a player to learn their role, the witch chooses a decide if save the player).\n2. Day Phase: During the day, all surviving players discuss who they suspect might be a werewolf. No one reveals their role unless it serves a strategic purpose. After the discussion, a vote is taken, and the player with the most votes is \"lynched\" or eliminated from the game.\n\nVICTORY CONDITION:\nFor werewolves, they win the game if the number of werewolves is equal to or greater than the number of remaining villagers.\nFor villagers, they win if they identify and eliminate all of the werewolves in the group.\n\nCONSTRAINTS:\n1. Your response should be in the first person.\n2. This is a conversational game. You should respond only based on the conversation history and your strategy.\n\nYou are playing werewolf in this game.\n",
"model_config_name": "gpt-3.5-turbo",
"use_memory": true
}
}
```
In this configuration, `Player1` is designated as a `DictDialogAgent`. The parameters include a system prompt (`sys_prompt`) that can guide the agent's behavior, a model config name (`model_config_name`) that determines the name of the model configuration, and a flag (`use_memory`) indicating whether the agent should remember past interactions.
For other players, configurations can be customized based on their roles. Each role may have different prompts, models, or memory settings. You can refer to the JSON file located at `examples/game_werewolf/configs/agent_configs.json` within the AgentScope examples directory.
### Step 3: Initialize AgentScope and the Agents
Now we have defined the roles in the application and we can initialize the AgentScope environment and all agents. This process is simplified by AgentScope via a few lines, based on the configuration files we've prepared (assuming there are **2** werewolves, **2** villagers, **1** witch, and **1** seer):
```python
import agentscope
# read model and agent configs, and initialize agents automatically
survivors = agentscope.init(
model_configs="./configs/model_configs.json",
agent_configs="./configs/agent_configs.json",
logger_level="DEBUG",
)
# Define the roles within the game. This list should match the order and number
# of agents specified in the 'agent_configs.json' file.
roles = ["werewolf", "werewolf", "villager", "villager", "seer", "witch"]
# Based on their roles, assign the initialized agents to variables.
# This helps us reference them easily in the game logic.
wolves, villagers, witch, seer = survivors[:2], survivors[2:-2], survivors[-1], survivors[-2]
```
Through this snippet of code, we've allocated roles to our agents and associated them with the configurations that dictate their behavior in the application.
### Step 4: Set Up the Game Logic
In this step, you will set up the game logic and orchestrate the flow of the Werewolf game using AgentScope's helper utilities.
#### Parser
In order to allow `DictDialogAgent` to output fields customized by the users, and to increase the success rate of parsing different fields by LLMs, we have added the `parser` module. Here is the configuration of a parser example:
```
to_wolves_vote = "Which player do you vote to kill?"
wolves_vote_parser = MarkdownJsonDictParser(
content_hint={
"thought": "what you thought",
"vote": "player_name",
},
required_keys=["thought", "vote"],
keys_to_memory="vote",
keys_to_content="vote",
)
```
For more details about the `parser` moduleplease see [here](https://modelscope.github.io/agentscope/en/tutorial/203-parser.html).
#### Leverage Pipeline and MsgHub
To simplify the construction of agent communication, AgentScope provides two helpful concepts: **Pipeline** and **MsgHub**.
- **Pipeline**: It allows users to program communication among agents easily.
```python
from agentscope.pipelines import SequentialPipeline
pipe = SequentialPipeline(agent1, agent2, agent3)
x = pipe(x) # the message x will be passed and replied by agent 1,2,3 in order
```
- **MsgHub**: You may have noticed that all the above examples are one-to-one communication. To achieve a group chat, we provide another communication helper utility `msghub`. With it, the messages from participants will be broadcast to all other participants automatically. In such cases, participating agents even don't need input and output messages. All we need to do is to decide the order of speaking. Besides, `msghub` also supports dynamic control of participants.
```python
with msghub(participants=[agent1, agent2, agent3]) as hub:
agent1()
agent2()
# Broadcast a message to all participants
hub.broadcast(Msg("Host", "Welcome to join the group chat!"))
# Add or delete participants dynamically
hub.delete(agent1)
hub.add(agent4)
```
#### Implement Werewolf Pipeline
The game logic is divided into two major phases: (1) night when werewolves act, and (2) daytime when all players discuss and vote. Each phase will be handled by a section of code using pipelines to manage multi-agent communications.
- **1.1 Night Phase: Werewolves Discuss and Vote**
During the night phase, werewolves must discuss among themselves to decide on a target. The `msghub` function creates a message hub for the werewolves to communicate in, where every message sent by an agent is observable by all other agents within the `msghub`.
```python
# start the game
for i in range(1, MAX_GAME_ROUND + 1):
# Night phase: werewolves discuss
hint = HostMsg(content=Prompts.to_wolves.format(n2s(wolves)))
with msghub(wolves, announcement=hint) as hub:
set_parsers(wolves, Prompts.wolves_discuss_parser)
for _ in range(MAX_WEREWOLF_DISCUSSION_ROUND):
x = sequentialpipeline(wolves)
if x.metadata.get("finish_discussion", False):
break
```
After the discussion, werewolves proceed to vote for their target, and the majority's choice is determined. The result of the vote is then broadcast to all werewolves.
**Note**: the detailed prompts and utility functions can be found in `examples/game_werewolf`.
```python
# werewolves vote
set_parsers(wolves, Prompts.wolves_vote_parser)
hint = HostMsg(content=Prompts.to_wolves_vote)
votes = [extract_name_and_id(wolf(hint).content)[0] for wolf in wolves]
# broadcast the result to werewolves
dead_player = [majority_vote(votes)]
hub.broadcast(
HostMsg(content=Prompts.to_wolves_res.format(dead_player[0])),
)
```
- **1.2 Witch's Turn**
If the witch is still alive, she gets the opportunity to use her powers to either save the player chosen by the werewolves or use her poison.
```python
# Witch's turn
healing_used_tonight = False
if witch in survivors:
if healing:
# Witch decides whether to use the healing potion
hint = HostMsg(
content=Prompts.to_witch_resurrect.format_map(
{"witch_name": witch.name, "dead_name": dead_player[0]},
),
)
# Witch decides whether to use the poison
set_parsers(witch, Prompts.witch_resurrect_parser)
if witch(hint).metadata.get("resurrect", False):
healing_used_tonight = True
dead_player.pop()
healing = False
```
- **1.3 Seer's Turn**
The seer has a chance to reveal the true identity of a player. This information can be crucial for the villagers. The `observe()` function allows each agent to take note of a message without immediately replying to it.
```python
# Seer's turn
if seer in survivors:
# Seer chooses a player to reveal their identity
hint = HostMsg(
content=Prompts.to_seer.format(seer.name, n2s(survivors)),
)
set_parsers(seer, Prompts.seer_parser)
x = seer(hint)
player, idx = extract_name_and_id(x.content)
role = "werewolf" if roles[idx] == "werewolf" else "villager"
hint = HostMsg(content=Prompts.to_seer_result.format(player, role))
seer.observe(hint)
```
- **1.4 Update Alive Players**
Based on the actions taken during the night, the list of surviving players needs to be updated.
```python
# Update the list of survivors and werewolves after the night's events
survivors, wolves = update_alive_players(survivors, wolves, dead_player)
```
- **2.1 Daytime Phase: Discussion and Voting**
During the day, all players will discuss and then vote to eliminate a suspected werewolf.
```python
# Daytime discussion
with msghub(survivors, announcement=hints) as hub:
# Discuss
set_parsers(survivors, Prompts.survivors_discuss_parser)
x = sequentialpipeline(survivors)
# Vote
set_parsers(survivors, Prompts.survivors_vote_parser)
hint = HostMsg(content=Prompts.to_all_vote.format(n2s(survivors)))
votes = [extract_name_and_id(_(hint).content)[0] for _ in survivors]
vote_res = majority_vote(votes)
# Broadcast the voting result to all players
result = HostMsg(content=Prompts.to_all_res.format(vote_res))
hub.broadcast(result)
# Update the list of survivors and werewolves after the vote
survivors, wolves = update_alive_players(survivors, wolves, vote_res)
```
- **2.2 Check for Winning Conditions**
After each phase, the game checks if the werewolves or villagers have won.
```python
# Check if either side has won
if check_winning(survivors, wolves, "Moderator"):
break
```
- **2.3 Continue to the Next Round**
If neither werewolves nor villagers win, the game continues to the next round.
```python
# If the game hasn't ended, prepare for the next round
hub.broadcast(HostMsg(content=Prompts.to_all_continue))
```
These code blocks outline the core game loop for Werewolf using AgentScope's `msghub` and `pipeline`, which help to easily manage the operational logic of an application.
### Step 5: Run the Application
With the game logic and agents set up, you're ready to run the Werewolf game. By executing the `pipeline`, the game will proceed through the predefined phases, with agents interacting based on their roles and the strategies coded above:
```bash
cd examples/game_werewolf
python main.py # Assuming the pipeline is implemented in main.py
```
It is recommended that you start the game in [AgentScope Studio](https://modelscope.github.io/agentscope/en/tutorial/209-gui.html), where you
will see the following output in the corresponding link:
![s](https://img.alicdn.com/imgextra/i3/O1CN01n2Q2tR1aCFD2gpTdu_!!6000000003293-1-tps-960-482.gif)
[[Return to the top]](#104-usecase-en)

View File

@@ -0,0 +1,65 @@
(105-logging-en)=
# Logging
Welcome to the tutorial on logging in multi-agent applications with AgentScope. We'll also touch on how you can visualize these logs using a simple web interface. This guide will help you track the agent's interactions and system information in a clearer and more organized way.
## Logging
The logging utilities consist of a custom setup for the `loguru.logger`, which is an enhancement over Python's built-in `logging` module. We provide custom features:
- **Colored Output**: Assigns different colors to different speakers in a chat to enhance readability.
- **Redirecting Standard Error (stderr)**: Captures error messages and logs them with the `ERROR` level.
- **Custom Log Levels**: Adds a custom level called `CHAT` that is specifically designed for logging dialogue interactions.
- **Special Formatting**: Format logs with timestamps, levels, function names, and line numbers. Chat messages are formatted differently to stand out.
### Setting Up the Logger
We recommend setting up the logger via `agentscope.init`, and you can set the log level:
```python
import agentscope
LOG_LEVEL = Literal[
"CHAT",
"TRACE",
"DEBUG",
"INFO",
"SUCCESS",
"WARNING",
"ERROR",
"CRITICAL",
]
agentscope.init(..., logger_level="INFO")
```
### Logging a Chat Message
Logging chat messages helps keep a record of the conversation between agents. Here's how you can do it:
```python
# Log a simple string message.
logger.chat("Hello World!")
# Log a `msg` representing dialogue with a speaker and content.
logger.chat({"name": "User", "content": "Hello, how are you?"})
logger.chat({"name": "Agent", "content": "I'm fine, thank you!"})
```
### Logging a System information
System logs are crucial for tracking the application's state and identifying issues. Here's how to log different levels of system information:
```python
# Log general information useful for understanding the flow of the application.
logger.info("The dialogue agent has started successfully.")
# Log a warning message indicating a potential issue that isn't immediately problematic.
logger.warning("The agent is running slower than expected.")
# Log an error message when something has gone wrong.
logger.error("The agent encountered an unexpected error while processing a request.")
```
[[Return to the top]](#105-logging-en)

View File

@@ -0,0 +1,191 @@
(201-agent-en)=
# Agent
This tutorial helps you to understand the `Agent` in more depth and navigate through the process of crafting your own custom agent with AgentScope. We start by introducing the fundamental abstraction called `AgentBase`, which serves as the base class to maintain the general behaviors of all agents. Then, we will go through the *AgentPool*, an ensemble of pre-built, specialized agents, each designed with a specific purpose in mind. Finally, we will demonstrate how to customize your own agent, ensuring it fits the needs of your project.
## Understanding `AgentBase`
The `AgentBase` class is the architectural cornerstone for all agent constructs within the AgentScope. As the superclass of all custom agents, it provides a comprehensive template consisting of essential attributes and methods that underpin the core functionalities of any conversational agent.
Each AgentBase derivative is composed of several key characteristics:
* `memory`: This attribute enables agents to retain and recall past interactions, allowing them to maintain context in ongoing conversations. For more details about `memory`, we defer to [Memory and Message Management](205-memory).
* `model`: The model is the computational engine of the agent, responsible for making a response given existing memory and input. For more details about `model`, we defer to [Using Different Model Sources with Model API](#203-model).
* `sys_prompt` & `engine`: The system prompt acts as predefined instructions that guide the agent in its interactions; and the `engine` is used to dynamically generate a suitable prompt. For more details about them, we defer to [Prompt Engine](206-prompt).
* `to_dist`: Used to create a distributed version of the agent, to support efficient collaboration among multiple agents. Note that `to_dist` is a reserved field and will be automatically added to the initialization function of any subclass of `AgentBase`. For more details about `to_dist`, please refer to [Distribution](208-distribute).
In addition to these attributes, `AgentBase` endows agents with pivotal methods such as `observe` and `reply`:
* `observe()`: Through this method, an agent can take note of *message* without immediately replying, allowing it to update its memory based on the observed *message*.
* `reply()`: This is the primary method that developers must implement. It defines the agent's behavior in response to an incoming *message*, encapsulating the logic that results in the agent's output.
Besides, for unified interfaces and type hints, we introduce another base class `Operator`, which indicates performing some operation on input data by the `__call__` function. And we make `AgentBase` a subclass of `Operator`.
```python
class AgentBase(Operator):
# ... [code omitted for brevity]
def __init__(
self,
name: str,
sys_prompt: Optional[str] = None,
model_config_name: str = None,
use_memory: bool = True,
memory_config: Optional[dict] = None,
) -> None:
# ... [code omitted for brevity]
def observe(self, x: Union[Msg, Sequence[Msg]]) -> None:
# An optional method for updating the agent's internal state based on
# messages it has observed. This method can be used to enrich the
# agent's understanding and memory without producing an immediate
# response.
if self.memory:
self.memory.add(x)
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
# The core method to be implemented by custom agents. It defines the
# logic for processing an input message and generating a suitable
# response.
raise NotImplementedError(
f"Agent [{type(self).__name__}] is missing the required "
f'"reply" function.',
)
# ... [code omitted for brevity]
```
## Exploring the AgentPool
The *AgentPool* within AgentScope is a curated ensemble of ready-to-use, specialized agents. Each of these agents is tailored for a distinct role and comes equipped with default behaviors that address specific tasks. The *AgentPool* is designed to expedite the development process by providing various templates of `Agent`.
Below is a table summarizing the functionality of some of the key agents available in the Agent Pool:
| Agent Type | Description | Typical Use Cases |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------- |
| `AgentBase` | Serves as the superclass for all agents, providing essential attributes and methods. | The foundation for building any custom agent. |
| `DialogAgent` | Manages dialogues by understanding context and generating coherent responses. | Customer service bots, virtual assistants. |
| `DictDialogAgent` | Manages dialogues by understanding context and generating coherent responses, and the responses are in json format. | Customer service bots, virtual assistants. |
| `UserAgent` | Interacts with the user to collect input, generating messages that may include URLs or additional specifics based on required keys. | Collecting user input for agents |
| `TextToImageAgent` | An agent that convert user input text to image. | Converting text to image |
| `ReActAgent` | An agent class that implements the ReAct algorithm. | Solving complex tasks |
| *More to Come* | AgentScope is continuously expanding its pool with more specialized agents for diverse applications. | |
## Customizing Agents from the AgentPool
Customizing an agent from AgentPool enables you to tailor its functionality to meet the unique demands of your multi-agent application. You have the flexibility to modify existing agents with minimal effort by **adjusting configurations** and prompts or, for more extensive customization, you can engage in secondary development.
Below, we provide usages of how to configure various agents from the AgentPool:
### `DialogAgent`
* **Reply Method**: The `reply` method is where the main logic for processing input *message* and generating responses.
```python
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
# Additional processing steps can occur here
# Record the input if needed
if self.memory:
self.memory.add(x)
# Generate a prompt for the language model using the system prompt and memory
prompt = self.model.format(
Msg("system", self.sys_prompt, role="system"),
self.memory
and self.memory.get_memory()
or x, # type: ignore[arg-type]
)
# Invoke the language model with the prepared prompt
response = self.model(prompt).text
#Format the response and create a message object
msg = Msg(self.name, response, role="assistant")
# Print/speak the message in this agent's voice
self.speak(msg)
# Record the message to memory and return it
if self.memory:
self.memory.add(msg)
return msg
```
* **Usages:** To tailor a `DialogAgent` for a customer service bot:
```python
from agentscope.agents import DialogAgent
# Configuration for the DialogAgent
dialog_agent_config = {
"name": "ServiceBot",
"model_config_name": "gpt-3.5", # Specify the model used for dialogue generation
"sys_prompt": "Act as AI assistant to interact with the others. Try to "
"reponse on one line.\n", # Custom prompt for the agent
# Other configurations specific to the DialogAgent
}
# Create and configure the DialogAgent
service_bot = DialogAgent(**dialog_agent_config)
```
### `UserAgent`
* **Reply Method**: This method processes user input by prompting for content and if needed, additional keys and a URL. The gathered data is stored in a *message* object in the agent's memory for logging or later use and returns the message as a response.
```python
def reply(
self,
x: Optional[Union[Msg, Sequence[Msg]]] = None,
required_keys: Optional[Union[list[str], str]] = None,
) -> Msg:
# Check if there is initial data to be added to memory
if self.memory:
self.memory.add(x)
content = input(f"{self.name}: ") # Prompt the user for input
kwargs = {}
# Prompt for additional information based on the required keys
if required_keys is not None:
if isinstance(required_keys, str):
required_keys = [required_keys]
for key in required_keys:
kwargs[key] = input(f"{key}: ")
# Optionally prompt for a URL if required
url = None
if self.require_url:
url = input("URL: ")
# Create a message object with the collected input and additional details
msg = Msg(self.name, content=content, url=url, **kwargs)
# Add the message object to memory
if self.memory:
self.memory.add(msg)
return msg
```
* **Usages:** To configure a `UserAgent` for collecting user input and URLs (of file, image, video, audio , or website):
```python
from agentscope.agents import UserAgent
# Configuration for UserAgent
user_agent_config = {
"name": "User",
"require_url": True, # If true, the agent will require a URL
}
# Create and configure the UserAgent
user_proxy_agent = UserAgent(**user_agent_config)
```
[[Return to the top]](#201-agent-en)

View File

@@ -0,0 +1,301 @@
(202-pipeline-en)=
# Pipeline and MsgHub
**Pipeline & MsgHub** (message hub) are one or a sequence of steps describing how the structured `Msg` passes between multi-agents, which streamlines the process of collaboration across agents.
`Pipeline` allows users to program communication among agents easily, and `MsgHub` enables message sharing among agents like a group chat.
## Pipelines
`Pipeline` in AgentScope serves as conduits through which messages pass among agents. In AgentScope, an `Agent` is a subclass of an `Operator` that performs some operation on input data. Pipelines extend this concept by encapsulating multiple agents, and also act as an `Operator`.
Here is the base class for all pipeline types:
```python
class PipelineBase(Operator):
"""Base interface of all pipelines."""
# ... [code omitted for brevity]
@abstractmethod
def __call__(self, x: Optional[dict] = None) -> dict:
"""Define the actions taken by this pipeline.
Args:
x (Optional[`dict`], optional):
Dialog history and some environmental information
Returns:
`dict`: The pipeline's response to the input.
"""
```
### Category
AgentScope provides two main types of pipelines based on their implementation strategy:
* **Operator-Type Pipelines**
* These pipelines are object-oriented and inherit from the `PipelineBase`. They are operators themselves and can be combined with other operators to create complex interaction patterns.
```python
# Instantiate and invoke
pipeline = ClsPipeline(agent1, agent2, agent3)
x = pipeline(x)
```
* **Functional Pipelines**
* Functional pipelines provide similar control flow mechanisms as the class-based pipelines but are implemented as standalone functions. These are useful for scenarios where a class-based setup may not be necessary or preferred.
```python
# Just invoke
x = funcpipeline(agent1, agent2, agent3, x)
```
Pipelines are categorized based on their functionality, much like programming language constructs. The table below outlines the different pipelines available in AgentScope:
| Operator-Type Pipeline | Functional Pipeline | Description |
| -------------------- | -------------------- | ------------------------------------------------------------ |
| `SequentialPipeline` | `sequentialpipeline` | Executes a sequence of operators in order, passing the output of one as the input to the next. |
| `IfElsePipeline` | `ifelsepipeline` | Implements conditional logic, executing one operator if a condition is true and another if it is false. |
| `SwitchPipeline` | `switchpipeline` | Facilitates multi-branch selection, executing an operator from a mapped set based on the evaluation of a condition. |
| `ForLoopPipeline` | `forlooppipeline` | Repeatedly executes an operator for a set number of iterations or until a specified break condition is met. |
| `WhileLoopPipeline` | `whilelooppipeline` | Continuously executes an operator as long as a given condition remains true. |
| - | `placeholder` | Acts as a placeholder in branches that do not require any operations in flow control like if-else/switch |
### Usage
This section illustrates how pipelines can simplify the implementation of logic in multi-agent applications by comparing the usage of pipelines versus approaches without pipelines.
**Note** Please note that in the examples provided below, we use the term `agent` to represent any instance that can act as an `Operator`. This is for ease of understanding and to illustrate how pipelines orchestrate interactions between different operations. You can replace `agent` with any `Operator`, thus allowing for a mix of `agent` and `pipeline` in practice.
#### `SequentialPipeline`
* Without pipeline:
```python
x = agent1(x)
x = agent2(x)
x = agent3(x)
```
* Using pipeline:
```python
from agentscope.pipelines import SequentialPipeline
pipe = SequentialPipeline([agent1, agent2, agent3])
x = pipe(x)
```
* Using functional pipeline:
```python
from agentscope.pipelines import sequentialpipeline
x = sequentialpipeline([agent1, agent2, agent3], x)
```
#### `IfElsePipeline`
* Without pipeline:
```python
if condition(x):
x = agent1(x)
else:
x = agent2(x)
```
* Using pipeline:
```python
from agentscope.pipelines import IfElsePipeline
pipe = IfElsePipeline(condition, agent1, agent2)
x = pipe(x)
```
* Using functional pipeline:
```python
from agentscope.functional import ifelsepipeline
x = ifelsepipeline(condition, agent1, agent2, x)
```
#### `SwitchPipeline`
* Without pipeline:
```python
switch_result = condition(x)
if switch_result == case1:
x = agent1(x)
elif switch_result == case2:
x = agent2(x)
else:
x = default_agent(x)
```
* Using pipeline:
```python
from agentscope.pipelines import SwitchPipeline
case_operators = {case1: agent1, case2: agent2}
pipe = SwitchPipeline(condition, case_operators, default_agent)
x = pipe(x)
```
* Using functional pipeline:
```python
from agentscope.functional import switchpipeline
case_operators = {case1: agent1, case2: agent2}
x = switchpipeline(condition, case_operators, default_agent, x)
```
#### `ForLoopPipeline`
* Without pipeline:
```python
for i in range(max_iterations):
x = agent(x)
if break_condition(x):
break
```
* Using pipeline:
```python
from agentscope.pipelines import ForLoopPipeline
pipe = ForLoopPipeline(agent, max_iterations, break_condition)
x = pipe(x)
```
* Using functional pipeline:
```python
from agentscope.functional import forlooppipeline
x = forlooppipeline(agent, max_iterations, break_condition, x)
```
#### `WhileLoopPipeline`
* Without pipeline:
```python
while condition(x):
x = agent(x)
```
* Using pipeline:
```python
from agentscope.pipelines import WhileLoopPipeline
pipe = WhileLoopPipeline(agent, condition)
x = pipe(x)
```
* Using functional pipeline:
```python
from agentscope.functional import whilelooppipeline
x = whilelooppipeline(agent, condition, x)
```
### Pipeline Combination
It's worth noting that AgentScope supports the combination of pipelines to create complex interactions. For example, we can create a pipeline that executes a sequence of agents in order, and then executes another pipeline that executes a sequence of agents in condition.
```python
from agentscope.pipelines import SequentialPipeline, IfElsePipeline
# Create a pipeline that executes agents in order
pipe1 = SequentialPipeline([agent1, agent2, agent3])
# Create a pipeline that executes agents in ifElsePipeline
pipe2 = IfElsePipeline(condition, agent4, agent5)
# Create a pipeline that executes pipe1 and pipe2 in order
pipe3 = SequentialPipeline([pipe1, pipe2])
# Invoke the pipeline
x = pipe3(x)
```
## MsgHub
`MsgHub` is designed to manage dialogue among a group of agents, allowing for the sharing of messages. Through `MsgHub`, agents can broadcast messages to all other agents in the group with `broadcast`.
Here is the core class for a `MsgHub`:
```python
class MsgHubManager:
"""MsgHub manager class for sharing dialog among a group of agents."""
# ... [code omitted for brevity]
def broadcast(self, msg: Union[dict, list[dict]]) -> None:
"""Broadcast the message to all participants."""
for agent in self.participants:
agent.observe(msg)
def add(self, new_participant: Union[Sequence[AgentBase], AgentBase]) -> None:
"""Add new participant into this hub"""
# ... [code omitted for brevity]
def delete(self, participant: Union[Sequence[AgentBase], AgentBase]) -> None:
"""Delete agents from participant."""
# ... [code omitted for brevity]
```
### Usage
#### Creating a MsgHub
To create a `MsgHub`, instantiate a `MsgHubManager` by calling the `msghub` helper function with a list of participating agents. Additionally, you can supply an optional initial announcement that, if provided, will be broadcast to all participants upon initialization.
```python
from agentscope.msg_hub import msghub
# Initialize MsgHub with participating agents
hub_manager = msghub(
participants=[agent1, agent2, agent3], announcement=initial_announcement
)
```
#### Broadcast message in MsgHub
The `MsgHubManager` can be used with a context manager to handle the setup and teardown of the message hub environment:
```python
with msghub(
participants=[agent1, agent2, agent3], announcement=initial_announcement
) as hub:
# Agents can now broadcast and receive messages within this block
agent1()
agent2()
# Or manually broadcast a message
hub.broadcast(some_message)
```
Upon exiting the context block, the `MsgHubManager` ensures that each agent's audience is cleared, preventing any unintended message sharing outside of the hub context.
#### Adding and Deleting Participants
You can dynamically add or remove agents from the `MsgHub`:
```python
# Add a new participant
hub.add(new_agent)
# Remove an existing participant
hub.delete(existing_agent)
```
[[Return to the top]](#202-pipeline-en)

View File

@@ -0,0 +1,605 @@
(203-model-en)=
# Model
In AgentScope, the model deployment and invocation are decoupled by `ModelWrapper`.
Developers can specify their own model by providing model configurations,
and AgentScope also provides scripts to support developers to customize
model services.
## Supported Models
Currently, AgentScope supports the following model service APIs:
- OpenAI API, including chat, image generation (DALL-E), and Embedding.
- DashScope API, including chat, image sythesis and text embedding.
- Gemini API, including chat and embedding.
- ZhipuAI API, including chat and embedding.
- Ollama API, including chat, embedding and generation.
- LiteLLM API, including chat, with various model APIs.
- Post Request API, model inference services based on Post
requests, including Huggingface/ModelScope Inference API and various
post request based model APIs.
## Configuration
In AgentScope, users specify the model configuration through the
`model_configs` parameter in the `agentscope.init` interface.
`model_configs` can be a **dictionary**, **a list of dictionaries**, or a
**path** to model configuration file.
```python
import agentscope
agentscope.init(model_configs=MODEL_CONFIG_OR_PATH)
```
### Configuration Format
In AgentScope, the model configuration is a dictionary used to specify the type of model and set the call parameters.
We divide the fields in the model configuration into two categories: _basic parameters_ and _detailed parameters_.
Among them, the basic parameters include `config_name` and `model_type`, which are used to distinguish different model configurations and specific `ModelWrapper` types.
The detailed parameters will be fed into the corresponding model class's constructor to initialize the model instance.
```python
{
# Basic parameters
"config_name": "gpt-4-temperature-0.0", # Model configuration name
"model_type": "openai_chat", # Correspond to `ModelWrapper` type
# Detailed parameters
# ...
}
```
#### Basic Parameters
In basic parameters, `config_name` is the identifier of the model configuration,
which we will use to specify the model service when initializing an agent.
`model_type` corresponds to the type of `ModelWrapper` and is used to specify the type of model service.
It corresponds to the `model_type` field in the `ModelWrapper` class in the source code.
```python
class OpenAIChatWrapper(OpenAIWrapperBase):
"""The model wrapper for OpenAI's chat API."""
model_type: str = "openai_chat"
# ...
```
In the current AgentScope, the supported `model_type` types, the corresponding
`ModelWrapper` classes, and the supported APIs are as follows:
| API | Task | Model Wrapper | `model_type` | Some Supported Models |
|------------------------|-----------------|---------------------------------------------------------------------------------------------------------------------------------|-------------------------------|--------------------------------------------------|
| OpenAI API | Chat | [`OpenAIChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/openai_model.py) | `"openai_chat"` | gpt-4, gpt-3.5-turbo, ... |
| | Embedding | [`OpenAIEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/openai_model.py) | `"openai_embedding"` | text-embedding-ada-002, ... |
| | DALL·E | [`OpenAIDALLEWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/openai_model.py) | `"openai_dall_e"` | dall-e-2, dall-e-3 |
| DashScope API | Chat | [`DashScopeChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py) | `"dashscope_chat"` | qwen-plus, qwen-max, ... |
| | Image Synthesis | [`DashScopeImageSynthesisWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py) | `"dashscope_image_synthesis"` | wanx-v1 |
| | Text Embedding | [`DashScopeTextEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py) | `"dashscope_text_embedding"` | text-embedding-v1, text-embedding-v2, ... |
| | Multimodal | [`DashScopeMultiModalWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py) | `"dashscope_multimodal"` | qwen-vl-plus, qwen-vl-max, qwen-audio-turbo, ... |
| Gemini API | Chat | [`GeminiChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/gemini_model.py) | `"gemini_chat"` | gemini-pro, ... |
| | Embedding | [`GeminiEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/gemini_model.py) | `"gemini_embedding"` | models/embedding-001, ... |
| ZhipuAI API | Chat | [`ZhipuAIChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/zhipu_model.py) | `"zhipuai_chat"` | glm4, ... |
| | Embedding | [`ZhipuAIEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/zhipu_model.py) | `"zhipuai_embedding"` | embedding-2, ... |
| ollama | Chat | [`OllamaChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/ollama_model.py) | `"ollama_chat"` | llama2, ... |
| | Embedding | [`OllamaEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/ollama_model.py) | `"ollama_embedding"` | llama2, ... |
| | Generation | [`OllamaGenerationWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/ollama_model.py) | `"ollama_generate"` | llama2, ... |
| LiteLLM API | Chat | [`LiteLLMChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/litellm_model.py) | `"litellm_chat"` | - |
| Post Request based API | - | [`PostAPIModelWrapperBase`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/post_model.py) | `"post_api"` | - |
| | Chat | [`PostAPIChatWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/post_model.py) | `"post_api_chat"` | meta-llama/Meta-Llama-3-8B-Instruct, ... |
| | Image Synthesis | [`PostAPIDALLEWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/post_model.py) | `post_api_dall_e` | - | |
| | Embedding | [`PostAPIEmbeddingWrapper`](https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/post_model.py) | `post_api_embedding` | - |
#### Detailed Parameters
In AgentScope, the detailed parameters are different according to the different `ModelWrapper` classes.
To specify the detailed parameters, you need to refer to the specific `ModelWrapper` class and its constructor.
Here we provide example configurations for different model wrappers.
##### OpenAI API
<details>
<summary>OpenAI Chat API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/openai_model.py">agents.models.OpenAIChatWrapper</a></code>)</summary>
```python
{
"config_name": "{your_config_name}",
"model_type": "openai_chat",
# Required parameters
"model_name": "gpt-4",
# Optional parameters
"api_key": "{your_api_key}", # OpenAI API Key, if not provided, it will be read from the environment variable
"organization": "{your_organization}", # Organization name, if not provided, it will be read from the environment variable
"client_args": { # Parameters for initializing the OpenAI API Client
# e.g. "max_retries": 3,
},
"generate_args": { # Parameters passed to the model when calling
# e.g. "temperature": 0.0
},
"budget": 100 # API budget
}
```
</details>
<details>
<summary>OpenAI DALL·E API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/openai_model.py">agentscope.models.OpenAIDALLEWrapper</a></code>)</summary>
```python
{
"config_name": "{your_config_name}",
"model_type": "openai_dall_e",
# Required parameters
"model_name": "{model_name}", # OpenAI model name, e.g. dall-e-2, dall-e-3
# Optional parameters
"api_key": "{your_api_key}", # OpenAI API Key, if not provided, it will be read from the environment variable
"organization": "{your_organization}", # Organization name, if not provided, it will be read from the environment variable
"client_args": { # Parameters for initializing the OpenAI API Client
# e.g. "max_retries": 3,
},
"generate_args": { # Parameters passed to the model when calling
# e.g. "n": 1, "size": "512x512"
}
}
```
</details>
<details>
<summary>OpenAI Embedding API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/openai_model.py">agentscope.models.OpenAIEmbeddingWrapper</a></code>)</summary>
```python
{
"config_name": "{your_config_name}",
"model_type": "openai_embedding",
# Required parameters
"model_name": "{model_name}", # OpenAI model name, e.g. text-embedding-ada-002, text-embedding-3-small
# Optional parameters
"api_key": "{your_api_key}", # OpenAI API Key, if not provided, it will be read from the environment variable
"organization": "{your_organization}", # Organization name, if not provided, it will be read from the environment variable
"client_args": { # Parameters for initializing the OpenAI API Client
# e.g. "max_retries": 3,
},
"generate_args": { # Parameters passed to the model when calling
# e.g. "encoding_format": "float"
}
}
```
</details>
<br/>
#### DashScope API
<details>
<summary>DashScope Chat API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py">agentscope.models.DashScopeChatWrapper</a></code>)</summary>
```python
{
"config_name": "my_dashscope_chat_config",
"model_type": "dashscope_chat",
# Required parameters
"model_name": "{model_name}", # The model name in DashScope API, e.g. qwen-max
# Optional parameters
"api_key": "{your_api_key}", # DashScope API Key, if not provided, it will be read from the environment variable
"generate_args": {
# e.g. "temperature": 0.5
},
}
```
</details>
<details>
<summary>DashScope Image Synthesis API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py">agentscope.models.DashScopeImageSynthesisWrapper</a></code>)</summary>
```python
{
"config_name": "my_dashscope_image_synthesis_config",
"model_type": "dashscope_image_synthesis",
# Required parameters
"model_name": "{model_name}", # The model name in DashScope Image Synthesis API, e.g. wanx-v1
# Optional parameters
"api_key": "{your_api_key}",
"generate_args": {
"negative_prompt": "xxx",
"n": 1,
# ...
}
}
```
</details>
<details>
<summary>DashScope Text Embedding API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py">agentscope.models.DashScopeTextEmbeddingWrapper</a></code>)</summary>
```python
{
"config_name": "my_dashscope_text_embedding_config",
"model_type": "dashscope_text_embedding",
# Required parameters
"model_name": "{model_name}", # The model name in DashScope Text Embedding API, e.g. text-embedding-v1
# Optional parameters
"api_key": "{your_api_key}",
"generate_args": {
# ...
},
}
```
</details>
<details>
<summary>DashScope Multimodal Conversation API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/dashscope_model.py">agentscope.models.DashScopeMultiModalWrapper</a></code>)</summary>
```python
{
"config_name": "my_dashscope_multimodal_config",
"model_type": "dashscope_multimodal",
# Required parameters
"model_name": "{model_name}", # The model name in DashScope Multimodal Conversation API, e.g. qwen-vl-plus
# Optional parameters
"api_key": "{your_api_key}",
"generate_args": {
# ...
},
}
```
</details>
<br/>
#### Gemini API
<details>
<summary>Gemini Chat API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/gemini_model.py">agentscope.models.GeminiChatWrapper</a></code>)</summary>
```python
{
"config_name": "my_gemini_chat_config",
"model_type": "gemini_chat",
# Required parameters
"model_name": "{model_name}", # The model name in Gemini API, e.g. gemini-pro
# Optional parameters
"api_key": "{your_api_key}", # If not provided, the API key will be read from the environment variable GEMINI_API_KEY
}
```
</details>
<details>
<summary>Gemini Embedding API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/gemini_model.py">agentscope.models.GeminiEmbeddingWrapper</a></code>)</summary>
```python
{
"config_name": "my_gemini_embedding_config",
"model_type": "gemini_embedding",
# Required parameters
"model_name": "{model_name}", # The model name in Gemini API, e.g. models/embedding-001
# Optional parameters
"api_key": "{your_api_key}", # If not provided, the API key will be read from the environment variable GEMINI_API_KEY
}
```
</details>
<br/>
#### ZhipuAI API
<details>
<summary>ZhipuAI Chat API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/zhipu_model.py">agentscope.models.ZhipuAIChatWrapper</a></code>)</summary>
```python
{
"config_name": "my_zhipuai_chat_config",
"model_type": "zhipuai_chat",
# Required parameters
"model_name": "{model_name}", # The model name in ZhipuAI API, e.g. glm-4
# Optional parameters
"api_key": "{your_api_key}"
}
```
</details>
<details>
<summary>ZhipuAI Embedding API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/zhipu_model.py">agentscope.models.ZhipuAIEmbeddingWrapper</a></code>)</summary>
```python
{
"config_name": "my_zhipuai_embedding_config",
"model_type": "zhipuai_embedding",
# Required parameters
"model_name": "{model_name}", # The model name in ZhipuAI API, e.g. embedding-2
# Optional parameters
"api_key": "{your_api_key}",
}
```
</details>
<br/>
#### Ollama API
<details>
<summary>Ollama Chat API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/ollama_model.py">agentscope.models.OllamaChatWrapper</a></code>)</summary>
```python
{
"config_name": "my_ollama_chat_config",
"model_type": "ollama_chat",
# Required parameters
"model_name": "{model_name}", # The model name used in ollama API, e.g. llama2
# Optional parameters
"options": { # Parameters passed to the model when calling
# e.g. "temperature": 0., "seed": 123,
},
"keep_alive": "5m", # Controls how long the model will stay loaded into memory
}
```
</details>
<details>
<summary>Ollama Generation API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/ollama_model.py">agentscope.models.OllamaGenerationWrapper</a></code>)</summary>
```python
{
"config_name": "my_ollama_generate_config",
"model_type": "ollama_generate",
# Required parameters
"model_name": "{model_name}", # The model name used in ollama API, e.g. llama2
# Optional parameters
"options": { # Parameters passed to the model when calling
# "temperature": 0., "seed": 123,
},
"keep_alive": "5m", # Controls how long the model will stay loaded into memory
}
```
</details>
<details>
<summary>Ollama Embedding API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/ollama_model.py">agentscope.models.OllamaEmbeddingWrapper</a></code>)</summary>
```python
{
"config_name": "my_ollama_embedding_config",
"model_type": "ollama_embedding",
# Required parameters
"model_name": "{model_name}", # The model name used in ollama API, e.g. llama2
# Optional parameters
"options": { # Parameters passed to the model when calling
# "temperature": 0., "seed": 123,
},
"keep_alive": "5m", # Controls how long the model will stay loaded into memory
}
```
</details>
<br/>
#### LiteLLM Chat API
<details>
<summary>LiteLLM Chat API (<code><a href="https://github.
com/modelscope/agentscope/blob/main/src/agentscope/models/litellm_model.py">agentscope.models.LiteLLMChatModelWrapper</a></code>)</summary>
```python
{
"config_name": "lite_llm_openai_chat_gpt-3.5-turbo",
"model_type": "litellm_chat",
"model_name": "gpt-3.5-turbo" # You should note that for different models, you should set the corresponding environment variables, such as OPENAI_API_KEY, etc. You may refer to https://docs.litellm.ai/docs/ for this.
},
```
</details>
<br/>
#### Post Request API
<details>
<summary>Post Request Chat API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/post_model.py">agentscope.models.PostAPIChatWrapper</a></code>)</summary>
```python
{
"config_name": "my_postapichatwrapper_config",
"model_type": "post_api_chat",
# Required parameters
"api_url": "https://xxx.xxx",
"headers": {
# e.g. "Authorization": "Bearer xxx",
},
# Optional parameters
"messages_key": "messages",
}
```
> ⚠️ The Post Request Chat model wrapper (`PostAPIChatWrapper`) has the following properties:
> 1) The `.format()` function makes sure the input messages become a list of dicts.
> 2) The `._parse_response()` function assumes the generated text will be in `response["data"]["response"]["choices"][0]["message"]["content"]`
</details>
<details>
<summary>Post Request Image Synthesis API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/post_model.py">agentscope.models.PostAPIDALLEWrapper</a></code>)</summary>
```python
{
"config_name": "my_postapiwrapper_config",
"model_type": "post_api_dall_e",
# Required parameters
"api_url": "https://xxx.xxx",
"headers": {
# e.g. "Authorization": "Bearer xxx",
},
# Optional parameters
"messages_key": "messages",
}
```
> ⚠️ The Post Request Image Synthesis model wrapper (`PostAPIDALLEWrapper`) has the following properties:
> 1) The `._parse_response()` function assumes the generated image will be presented as urls in `response["data"]["response"]["data"][i]["url"]`
</details>
<details>
<summary>Post Request Embedding API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/post_model.py">agentscope.models.PostAPIEmbeddingWrapper</a></code>)</summary>
```python
{
"config_name": "my_postapiwrapper_config",
"model_type": "post_api_embedding",
# Required parameters
"api_url": "https://xxx.xxx",
"headers": {
# e.g. "Authorization": "Bearer xxx",
},
# Optional parameters
"messages_key": "messages",
}
```
> ⚠️ The Post Request Embedding model wrapper (`PostAPIEmbeddingWrapper`) has the following properties:
> 1) The `._parse_response()` function assumes the generated embeddings will be in `response["data"]["response"]["data"][i]["embedding"]`
</details>
<details>
<summary>Post Request API (<code><a href="https://github.com/modelscope/agentscope/blob/main/src/agentscope/models/post_model.py">agentscope.models.PostAPIModelWrapperBase</a></code>)</summary>
```python
{
"config_name": "my_postapiwrapper_config",
"model_type": "post_api",
# Required parameters
"api_url": "https://xxx.xxx",
"headers": {
# e.g. "Authorization": "Bearer xxx",
},
# Optional parameters
"messages_key": "messages",
}
```
> ⚠️ Post Request model wrapper (`PostAPIModelWrapperBase`) returns raw HTTP responses from the API in ModelResponse, and the `.format()` is not implemented. It is recommended to use `Post Request Chat API` when running examples with chats.
> `PostAPIModelWrapperBase` can be used when
> 1) only the raw HTTP response is wanted and `.format()` is not called;
> 2) Or, the developers want to overwrite the `.format()` and/or `._parse_response()` functions.
</details>
<br/>
## Build Model Service from Scratch
For developers who need to build their own model services, AgentScope
provides some scripts to help developers quickly build model services.
You can find these scripts and instructions in the [scripts](https://github.com/modelscope/agentscope/tree/main/scripts)
directory.
Specifically, AgentScope provides the following model service scripts:
- [CPU inference engine **ollama**](https://github.com/modelscope/agentscope/blob/main/scripts/README.md#ollama)
- [Model service based on **Flask + Transformers**](https://github.com/modelscope/agentscope/blob/main/scripts/README.md#with-transformers-library)
- [Model service based on **Flask + ModelScope**](https://github.com/modelscope/agentscope/blob/main/scripts/README.md#with-modelscope-library)
- [**FastChat** inference engine](https://github.com/modelscope/agentscope/blob/main/scripts/README.md#fastchat)
- [**vllm** inference engine](https://github.com/modelscope/agentscope/blob/main/scripts/README.md#vllm)
About how to quickly start these model services, users can refer to the [README.md](https://github.com/modelscope/agentscope/blob/main/scripts/README.md) file under the [scripts](https://github.com/modelscope/agentscope/blob/main/scripts/) directory.
## Creat Your Own Model Wrapper
AgentScope allows developers to customize their own model wrappers.
The new model wrapper class should
- inherit from `ModelWrapperBase` class,
- provide a `model_type` field to identify this model wrapper in the model configuration, and
- implement its `__init__` and `__call__` functions.
The following is an example for creating a new model wrapper class.
```python
from agentscope.models import ModelWrapperBase
class MyModelWrapper(ModelWrapperBase):
model_type: str = "my_model"
def __init__(self, config_name, my_arg1, my_arg2, **kwargs):
# Initialize the model instance
super().__init__(config_name=config_name)
# ...
def __call__(self, input, **kwargs) -> str:
# Call the model instance
# ...
```
After creating the new model wrapper class, the model wrapper will be registered into AgentScope automatically.
You can use it in the model configuration directly.
```python
my_model_config = {
# Basic parameters
"config_name": "my_model_config",
"model_type": "my_model",
# Detailed parameters
"my_arg1": "xxx",
"my_arg2": "yyy",
# ...
}
```
[[Return to Top]](#203-model-en)

View File

@@ -0,0 +1,506 @@
(203-parser-en)=
# Response Parser
## Table of Contents
- [Background](#background)
- [Parser Module](#parser-module)
- [Overview](#overview)
- [String Type](#string-type)
- [MarkdownCodeBlockParser](#markdowncodeblockparser)
- [Initialization](#initialization)
- [Format Instruction Template](#format-instruction-template)
- [Parse Function](#parse-function)
- [Dictionary Type](#dictionary-type)
- [MarkdownJsonDictParser](#markdownjsondictparser)
- [Initialization & Format Instruction Template](#initialization--format-instruction-template)
- [Validation](#validation)
- [MultiTaggedContentParser](#multitaggedcontentparser)
- [Initialization & Format Instruction Template](#initialization--format-instruction-template-1)
- [Parse Function](#parse-function-1)
- [JSON / Python Object Type](#json--python-object-type)
- [MarkdownJsonObjectParser](#markdownjsonobjectparser)
- [Initialization & Format Instruction Template](#initialization--format-instruction-template-2)
- [Parse Function](#parse-function-2)
- [Typical Use Cases](#typical-use-cases)
- [WereWolf Game](#werewolf-game)
- [ReAct Agent and Tool Usage](#react-agent-and-tool-usage)
- [Customized Parser](#customized-parser)
## Background
In the process of building LLM-empowered application, parsing the LLM generated string into a specific format and extracting the required information is a very important step.
However, due to the following reasons, this process is also a very complex process:
1. **Diversity**: The target format of parsing is diverse, and the information to be extracted may be a specific text, a JSON object, or a complex data structure.
2. **Complexity**: The result parsing is not only to convert the text generated by LLM into the target format, but also involves a series of issues such as prompt engineering (reminding LLM what format of output should be generated), error handling, etc.
3. **Flexibility**: Even in the same application, different stages may also require the agent to generate output in different formats.
For the convenience of developers, AgentScope provides a parser module to help developers parse LLM response into a specific format. By using the parser module, developers can easily parse the response into the target format by simple configuration, and switch the target format flexibly.
In AgentScope, the parser module features
1. **Flexibility**: Developers can flexibly set the required format, flexibly switch the parser without modifying the code of agent class. That is, the specific "target format" and the agent's `reply` function are decoupled.
2. **Freedom**: The format instruction, result parsing and prompt engineering are all explicitly finished in the `reply` function. Developers and users can freely choose to use the parser or parse LLM response by their own code.
3. **Transparency**: When using the parser, the process and results of prompt construction are completely visible and transparent to developers in the `reply` function, and developers can precisely debug their applications.
## Parser Module
### Overview
The main functions of the parser module include:
1. Provide "format instruction", that is, remind LLM where to generate what output, for example
````
You should generate python code in a fenced code block as follows
```python
{your_python_code}
```
````
2. Provide a parse function, which directly parses the text generated by LLM into the target data format,
3. Post-processing for dictionary format. After parsing the text into a dictionary, different fields may have different uses.
AgentScope provides multiple built-in parsers, and developers can choose according to their needs.
| Target Format | Parser Class | Description |
| --- | --- |------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| String | `MarkdownCodeBlockParser` | Requires LLM to generate specified text within a Markdown code block marked by ```. The result is a string. |
| Dictionary | `MarkdownJsonDictParser` | Requires LLM to produce a specified dictionary within the code block marked by \```json and \```. The result is a Python dictionary. |
| | `MultiTaggedContentParser` | Requires LLM to generate specified content within multiple tags. Contents from different tags will be parsed into a single Python dictionary with different key-value pairs. |
| JSON / Python Object Type | `MarkdownJsonObjectParser` | Requires LLM to produce specified content within the code block marked by \```json and \```. The result will be converted into a Python object via json.loads. |
> **NOTE**: Compared to `MarkdownJsonDictParser`, `MultiTaggedContentParser` is more suitable for weak LLMs and when the required format is too complex.
> For example, when LLM is required to generate Python code, if the code is returned directly within a dictionary, LLM needs to be aware of escaping characters (\t, \n, ...), and the differences between double and single quotes when calling `json.loads`
>
> In contrast, `MultiTaggedContentParser` guides LLM to generate each key-value pair separately in individual tags and then combines them into a dictionary, thus reducing the difficulty.
>**NOTE**: The built-in strategies to construct format instruction just provide some examples. In AgentScope, developer has complete control over prompt construction. So they can choose not to use the format instruction provided by parsers, customizing their format instruction by hand or implementing new parser class are all feasible.
In the following sections, we will introduce the usage of these parsers based on different target formats.
### String Type
#### MarkdownCodeBlockParser
##### Initialization
- `MarkdownCodeBlockParser` requires LLM to generate specific text within a specified code block in Markdown format. Different languages can be specified with the `language_name` parameter to utilize the large model's ability to produce corresponding outputs. For example, when asking the large model to produce Python code, initialize as follows:
```python
from agentscope.parsers import MarkdownCodeBlockParser
parser = MarkdownCodeBlockParser(language_name="python", content_hint="your python code")
```
##### Format Instruction Template
- `MarkdownCodeBlockParser` provides the following format instruction template. When the user calls the `format_instruction` attribute, `{language_name}` will be replaced with the string entered at initialization:
````
You should generate {language_name} code in a {language_name} fenced code block as follows:
```{language_name}
{content_hint}
```
````
- For the above initialization with `language_name` as `"python"`, when the `format_instruction` attribute is called, the following string will be returned:
```python
print(parser.format_instruction)
```
````
You should generate python code in a python fenced code block as follows
```python
your python code
```
````
##### Parse Function
- `MarkdownCodeBlockParser` provides a `parse` method to parse the text generated by LLM。Its input and output are both `ModelResponse` objects, and the parsing result will be mounted on the `parsed` attribute of the output object.
````python
res = parser.parse(
ModelResponse(
text="""The following is generated python code
```python
print("Hello world!")
```
"""
)
)
print(res.parsed)
````
```
print("hello world!")
```
### Dictionary Type
Different from string and general JSON/Python object, as a powerful format in LLM applications, AgentScope provides additional post-processing functions for dictionary type.
When initializing the parser, you can set the `keys_to_content`, `keys_to_memory`, and `keys_to_metadata` parameters to achieve filtering of key-value pairs when calling the parser's `to_content`, `to_memory`, and `to_metadata` methods.
- `keys_to_content` specifies the key-value pairs that will be placed in the `content` field of the returned `Msg` object. The content field will be returned to other agents, participate in their prompt construction, and will also be called by the `self.speak` function for display.
- `keys_to_memory` specifies the key-value pairs that will be stored in the memory of the agent.
- `keys_to_metadata` specifies the key-value pairs that will be placed in the `metadata` field of the returned `Msg` object, which can be used for application control flow judgment, or mount some information that does not need to be returned to other agents.
The three parameters receive bool values, string and a list of strings. The meaning of their values is as follows:
- `False`: The corresponding filter function will return `None`.
- `True`: The whole dictionary will be returned.
- `str`: The corresponding value will be directly returned.
- `List[str]`: A filtered dictionary will be returned according to the list of keys.
By default, `keys_to_content` and `keys_to_memory` are `True`, that is, the whole dictionary will be returned. `keys_to_metadata` defaults to `False`, that is, the corresponding filter function will return `None`.
For example, the dictionary generated by the werewolf in the daytime discussion in a werewolf game. In this example,
- `"thought"` should not be returned to other agents, but should be stored in the agent's memory to ensure the continuity of the werewolf strategy;
- `"speak"` should be returned to other agents and stored in the agent's memory;
- `"finish_discussion"` is used in the application's control flow to determine whether the discussion has ended. To save tokens, this field should not be returned to other agents or stored in the agent's memory.
```python
{
"thought": "The others didn't realize I was a werewolf. I should end the discussion soon.",
"speak": "I agree with you.",
"finish_discussion": True
}
```
In AgentScope, we achieve post-processing by calling the `to_content`, `to_memory`, and `to_metadata` methods, as shown in the following code:
- The code for the application's control flow, create the corresponding parser object and load it
```python
from agentscope.parsers import MarkdownJsonDictParser
# ...
agent = DictDialogAgent(...)
# Take MarkdownJsonDictParser as example
parser = MarkdownJsonDictParser(
content_hint={
"thought": "what you thought",
"speak": "what you speak",
"finish_discussion": "whether the discussion is finished"
},
keys_to_content="speak",
keys_to_memory=["thought", "speak"],
keys_to_metadata=["finish_discussion"]
)
# Load parser, which is equivalent to specifying the required format
agent.set_parser(parser)
# The discussion process
while True:
# ...
x = agent(x)
# Break the loop according to the finish_discussion field in metadata
if x.metadata["finish_discussion"]:
break
```
- Filter the dictionary in the agent's `reply` function
```python
# ...
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
# ...
res = self.model(prompt, parse_func=self.parser.parse)
# Story the thought and speak fields into memory
self.memory.add(
Msg(
self.name,
content=self.parser.to_memory(res.parsed),
role="assistant",
)
)
# Store in content and metadata fields in the returned Msg object
msg = Msg(
self.name,
content=self.parser.to_content(res.parsed),
role="assistant",
metadata=self.parser.to_metadata(res.parsed),
)
self.speak(msg)
return msg
```
> **Note**: `keys_to_content`, `keys_to_memory`, and `keys_to_metadata` parameters can be a string, a list of strings, or a bool value.
> - For `True`, the `to_content`, `to_memory`, and `to_metadata` methods will directly return the whole dictionary.
> - For `False`, the `to_content`, `to_memory`, and `to_metadata` methods will directly return `None`.
> - For a string, the `to_content`, `to_memory`, and `to_metadata` methods will directly extract the corresponding value. For example, if `keys_to_content="speak"`, the `to_content` method will put `res.parsed["speak"]` into the `content` field of the `Msg` object, and the `content` field will be a string rather than a dictionary.
> - For a list of string, the `to_content`, `to_memory`, and `to_metadata` methods will filter the dictionary according to the list of keys.
> ```python
> parser = MarkdownJsonDictParser(
> content_hint={
> "thought": "what you thought",
> "speak": "what you speak",
> },
> keys_to_content="speak",
> keys_to_memory=["thought", "speak"],
> )
>
> example_dict = {"thought": "abc", "speak": "def"}
> print(parser.to_content(example_dict)) # def
> print(parser.to_memory(example_dict)) # {"thought": "abc", "speak": "def"}
> print(parser.to_metadata(example_dict)) # None
> ```
> ```
> def
> {"thought": "abc", "speak": "def"}
> None
> ```
Next we will introduce two parsers for dictionary type.
#### MarkdownJsonDictParser
##### Initialization & Format Instruction Template
- `MarkdownJsonDictParser` requires LLM to generate dictionary within a code block fenced by \```json and \``` tags.
- Except `keys_to_content`, `keys_to_memory` and `keys_to_metadata`, the `content_hint` parameter can be provided to give an example and explanation of the response result, that is, to remind LLM where and what kind of dictionary should be generated.
This parameter can be a string or a dictionary. For dictionary, it will be automatically converted to a string when constructing the format instruction.
```python
from agentscope.parsers import MarkdownJsonDictParser
# dictionary as content_hint
MarkdownJsonDictParser(
content_hint={
"thought": "what you thought",
"speak": "what you speak",
}
)
# or string as content_hint
MarkdownJsonDictParser(
content_hint="""{
"thought": "what you thought",
"speak": "what you speak",
}"""
)
```
- The corresponding `instruction_format` attribute
````
You should respond a json object in a json fenced code block as follows:
```json
{content_hint}
```
````
##### Validation
The `content_hint` parameter in `MarkdownJsonDictParser` also supports type validation based on Pydantic. When initializing, you can set `content_hint` to a Pydantic model class, and AgentScope will modify the `instruction_format` attribute based on this class. Besides, Pydantic will be used to validate the dictionary returned by LLM during parsing.
A simple example is as follows, where `"..."` can be filled with specific type validation rules, which can be referred to the [Pydantic](https://docs.pydantic.dev/latest/) documentation.
```python
from pydantic import BaseModel, Field
from agentscope.parsers import MarkdownJsonDictParser
class Schema(BaseModel):
thought: str = Field(..., description="what you thought")
speak: str = Field(..., description="what you speak")
end_discussion: bool = Field(..., description="whether the discussion is finished")
parser = MarkdownJsonDictParser(content_hint=Schema)
```
- The corresponding `instruction_format` attribute
````
Respond a JSON dictionary in a markdown's fenced code block as follows:
```json
{a_JSON_dictionary}
```
The generated JSON dictionary MUST follow this schema:
{'properties': {'speak': {'description': 'what you speak', 'title': 'Speak', 'type': 'string'}, 'thought': {'description': 'what you thought', 'title': 'Thought', 'type': 'string'}, 'end_discussion': {'description': 'whether the discussion reached an agreement or not', 'title': 'End Discussion', 'type': 'boolean'}}, 'required': ['speak', 'thought', 'end_discussion'], 'title': 'Schema', 'type': 'object'}
````
- During the parsing process, Pydantic will be used for type validation, and an exception will be thrown if the validation fails. Meanwhile, Pydantic also provides some fault tolerance capabilities, such as converting the string `"true"` to Python's `True`:
````
parser.parser("""
```json
{
"thought": "The others didn't realize I was a werewolf. I should end the discussion soon.",
"speak": "I agree with you.",
"end_discussion": "true"
}
```
""")
````
#### MultiTaggedContentParser
`MultiTaggedContentParser` asks LLM to generate specific content within multiple tag pairs. The content from different tag pairs will be parsed into a single Python dictionary. Its usage is similar to `MarkdownJsonDictParser`, but the initialization method is different, and it is more suitable for weak LLMs or complex return content.
##### Initialization & Format Instruction Template
Within `MultiTaggedContentParser`, each tag pair will be specified by as `TaggedContent` object, which contains
- Tag name (`name`), the key value in the returned dictionary
- Start tag (`tag_begin`)
- Hint for content (`content_hint`)
- End tag (`tag_end`)
- Content parsing indication (`parse_json`), default as `False`. When set to `True`, the parser will automatically add hint that requires JSON object between the tags, and its extracted content will be parsed into a Python object via `json.loads`
```python
from agentscope.parsers import MultiTaggedContentParser, TaggedContent
parser = MultiTaggedContentParser(
TaggedContent(
name="thought",
tag_begin="[THOUGHT]",
content_hint="what you thought",
tag_end="[/THOUGHT]"
),
TaggedContent(
name="speak",
tag_begin="[SPEAK]",
content_hint="what you speak",
tag_end="[/SPEAK]"
),
TaggedContent(
name="finish_discussion",
tag_begin="[FINISH_DISCUSSION]",
content_hint="true/false, whether the discussion is finished",
tag_end="[/FINISH_DISCUSSION]",
parse_json=True, # we expect the content of this field to be parsed directly into a Python boolean value
)
)
print(parser.format_instruction)
```
```
Respond with specific tags as outlined below, and the content between [FINISH_DISCUSSION] and [/FINISH_DISCUSSION] MUST be a JSON object:
[THOUGHT]what you thought[/THOUGHT]
[SPEAK]what you speak[/SPEAK]
[FINISH_DISCUSSION]true/false, whether the discussion is finished[/FINISH_DISCUSSION]
```
##### Parse Function
- `MultiTaggedContentParser`'s parsing result is a dictionary, whose keys are the value of `name` in the `TaggedContent` objects.
The following is an example of parsing the LLM response in the werewolf game:
```python
res_dict = parser.parse(
ModelResponse(
text="""As a werewolf, I should keep pretending to be a villager
[THOUGHT]The others didn't realize I was a werewolf. I should end the discussion soon.[/THOUGHT]
[SPEAK]I agree with you.[/SPEAK]
[FINISH_DISCUSSION]true[/FINISH_DISCUSSION]"""
)
)
print(res_dict)
```
```
{
"thought": "The others didn't realize I was a werewolf. I should end the discussion soon.",
"speak": "I agree with you.",
"finish_discussion": true
}
```
### JSON / Python Object Type
#### MarkdownJsonObjectParser
`MarkdownJsonObjectParser` also uses the \```json and \``` tags in Markdown, but does not limit the content type. It can be a list, dictionary, number, string, etc., which can be parsed into a Python object via `json.loads`.
##### Initialization & Format Instruction Template
```python
from agentscope.parsers import MarkdownJsonObjectParser
parser = MarkdownJsonObjectParser(
content_hint="{A list of numbers.}"
)
print(parser.format_instruction)
```
````
You should respond a json object in a json fenced code block as follows:
```json
{a list of numbers}
```
````
##### Parse Function
````python
res = parser.parse(
ModelResponse(
text="""Yes, here is the generated list
```json
[1,2,3,4,5]
```
""")
)
print(type(res))
print(res)
````
```
<class 'list'>
[1, 2, 3, 4, 5]
```
## Typical Use Cases
### WereWolf Game
Werewolf game is a classic use case of dictionary parser. In different stages of the game, the same agent needs to generate different identification fields in addition to `"thought"` and `"speak"`, such as whether the discussion is over, whether the seer uses its ability, whether the witch uses the antidote and poison, and voting.
AgentScope has built-in examples of [werewolf game](https://github.com/modelscope/agentscope/tree/main/examples/game_werewolf), which uses `DictDialogAgent` class and different parsers to achieve flexible target format switching. By using the post-processing function of the parser, it separates "thought" and "speak", and controls the progress of the game successfully.
More details can be found in the werewolf game [source code](https://github.com/modelscope/agentscope/tree/main/examples/game_werewolf).
### ReAct Agent and Tool Usage
`ReActAgent` is an agent class built for tool usage in AgentScope, based on the ReAct algorithm, and can be used with different tool functions. The tool call, format parsing, and implementation of `ReActAgent` are similar to the parser. For detailed implementation, please refer to the [source code](https://github.com/modelscope/agentscope/blob/main/src/agentscope/agents/react_agent.py).
## Customized Parser
AgentScope provides a base class `ParserBase` for parsers. Developers can inherit this base class, and implement the `format_instruction` attribute and `parse` method to create their own parser.
For dictionary type parsing, you can also inherit the `agentscope.parser.DictFilterMixin` class to implement post-processing for dictionary type.
```python
from abc import ABC, abstractmethod
from agentscope.models import ModelResponse
class ParserBase(ABC):
"""The base class for model response parser."""
format_instruction: str
"""The instruction for the response format."""
@abstractmethod
def parse(self, response: ModelResponse) -> ModelResponse:
"""Parse the response text to a specific object, and stored in the
parsed field of the response object."""
# ...
```

View File

@@ -0,0 +1,332 @@
(204-service-en)=
# Tool
Service function is a set of multi-functional utility tools that can be
used to enhance the capabilities of agents, such as executing Python code,
web search, file operations, and more.
This tutorial provides an overview of the service functions available in
AgentScope and how to use them to enhance the capabilities of your agents.
## Built-in Service Functions
The following table outlines the various Service functions by type. These functions can be called using `agentscope.service.{function_name}`.
| Service Scene | Service Function Name | Description |
|-----------------------------|----------------------------|----------------------------------------------------------------------------------------------------------------|
| Code | `execute_python_code` | Execute a piece of Python code, optionally inside a Docker container. |
| Retrieval | `retrieve_from_list` | Retrieve a specific item from a list based on given criteria. |
| | `cos_sim` | Compute the cosine similarity between two different embeddings. |
| SQL Query | `query_mysql` | Execute SQL queries on a MySQL database and return results. |
| | `query_sqlite` | Execute SQL queries on a SQLite database and return results. |
| | `query_mongodb` | Perform queries or operations on a MongoDB collection. |
| Text Processing | `summarization` | Summarize a piece of text using a large language model to highlight its main points. |
| Web | `bing_search` | Perform bing search |
| | `google_search` | Perform google search |
| | `arxiv_search` | Perform arXiv search |
| | `download_from_url` | Download file from given URL. |
| | `load_web` | Load and parse the web page of the specified url (currently only supports HTML). |
| | `digest_webpage` | Digest the content of a already loaded web page (currently only supports HTML).
| | `dblp_search_publications` | Search publications in the DBLP database
| | `dblp_search_authors` | Search for author information in the DBLP database |
| | `dblp_search_venues` | Search for venue information in the DBLP database |
| File | `create_file` | Create a new file at a specified path, optionally with initial content. |
| | `delete_file` | Delete a file specified by a file path. |
| | `move_file` | Move or rename a file from one path to another. |
| | `create_directory` | Create a new directory at a specified path. |
| | `delete_directory` | Delete a directory and all its contents. |
| | `move_directory` | Move or rename a directory from one path to another. |
| | `read_text_file` | Read and return the content of a text file. |
| | `write_text_file` | Write text content to a file at a specified path. |
| | `read_json_file` | Read and parse the content of a JSON file. |
| | `write_json_file` | Serialize a Python object to JSON and write to a file. |
| Multi Modality | `dashscope_text_to_image` | Convert text to image using Dashscope API. |
| | `dashscope_image_to_text` | Convert image to text using Dashscope API. |
| | `dashscope_text_to_audio` | Convert text to audio using Dashscope API. |
| | `openai_text_to_image` | Convert text to image using OpenAI API
| | `openai_edit_image` | Edit an image based on the provided mask and prompt using OpenAI API
| | `openai_create_image_variation` | Create variations of an image using OpenAI API
| | `openai_image_to_text` | Convert text to image using OpenAI API
| | `openai_text_to_audio` | Convert text to audio using OpenAI API
| | `openai_audio_to_text` | Convert audio to text using OpenAI API
| *More services coming soon* | | More service functions are in development and will be added to AgentScope to further enhance its capabilities. |
About each service function, you can find detailed information in the
[API document](https://modelscope.github.io/agentscope/).
## How to use Service Functions
AgentScope provides two classes for service functions,
`ServiceToolkit` and `ServiceResponse`.
### About Service Toolkit
The use of tools for LLM usually involves five steps:
1. **Prepare tool functions**. That is, developers should pre-process the
functions by providing necessary parameters, e.g. api key, username,
password, etc.
2. **Prepare instruction for LLM**. A detailed description for these tool
functions are required for the LLM to understand them properly.
3. **Guide LLM how to use tool functions**. A format description for calling
functions is required.
4. **Parse LLM response**. Once the LLM generates a response,
we need to parse it according to above format in the third step.
5. **Call functions and handle exceptions**. Calling the functions, return
the results, and handle exceptions.
To simplify the above steps and improve reusability, AgentScope introduces
`ServiceToolkit`. It can
- register python functions
- generate tool function descriptions in both string and JSON schema format
- generate usage instruction for LLM
- parse the model response, call the tool functions, and handle exceptions
#### How to use
Follow the steps below to use `ServiceToolkit`:
1. Init a `ServiceToolkit` object and register service functions with necessary
parameters. Take the following Bing search function as an example.
```python
def bing_search(
question: str,
api_key: str,
num_results: int = 10,
**kwargs: Any,
) -> ServiceResponse:
"""
Search question in Bing Search API and return the searching results
Args:
question (`str`):
The search query string.
api_key (`str`):
The API key provided for authenticating with the Bing Search API.
num_results (`int`, defaults to `10`):
The number of search results to return.
**kwargs (`Any`):
Additional keyword arguments to be included in the search query.
For more details, please refer to
https://learn.microsoft.com/en-us/bing/search-apis/bing-web-search/reference/query-parameters
[omitted for brevity]
"""
```
We register the function in a `ServiceToolkit` object by providing `api_key` and `num_results` as necessary parameters.
```python
from agentscope.service import ServiceToolkit
service_toolkit = ServiceToolkit()
service_toolkit.add(
bing_search,
api_key="xxx",
num_results=3
)
```
2. Use the `tools_instruction` attribute to instruct LLM in prompt, or use the `json_schemas` attribute to get the JSON schema format descriptions to construct customized instruction or directly use in model APIs (e.g. OpenAI Chat API).
````text
>> print(service_toolkit.tools_instruction)
## Tool Functions:
The following tool functions are available in the format of
```
{index}. {function name}: {function description}
{argument1 name} ({argument type}): {argument description}
{argument2 name} ({argument type}): {argument description}
...
```
1. bing_search: Search question in Bing Search API and return the searching results
question (str): The search query string.
````
````text
>> print(service_toolkit.json_schemas)
{
"bing_search": {
"type": "function",
"function": {
"name": "bing_search",
"description": "Search question in Bing Search API and return the searching results",
"parameters": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The search query string."
}
},
"required": [
"question"
]
}
}
}
}
````
3. Guide LLM how to use tool functions by the `tools_calling_format` attribute.
The ServiceToolkit module requires LLM to return a list of dictionaries in
JSON format, where each dictionary represents a function call. It must
contain two fields, `name` and `arguments`, where `name` is the function name
and `arguments` is a dictionary that maps from the argument name to the
argument value.
```text
>> print(service_toolkit.tools_calling_format)
[{"name": "{function name}", "arguments": {"{argument1 name}": xxx, "{argument2 name}": xxx}}]
```
4. Parse the LLM response and call functions by its `parse_and_call_func`
method. This function takes a string or a parsed dictionary as input.
- When the input is a string, this function will parse it accordingly and execute the function with the parsed arguments.
- While if the input is a parse dictionary, it will call the function directly.
```python
# a string input
string_input = '[{"name": "bing_search", "arguments": {"question": "xxx"}}]'
res_of_string_input = service_toolkit.parse_and_call_func(string_input)
# or a parsed dictionary
dict_input = [{"name": "bing_search", "arguments": {"question": "xxx"}}]
# res_of_dict_input is the same as res_of_string_input
res_of_dict_input = service_toolkit.parse_and_call_func(dict_input)
print(res_of_string_input)
```
```
1. Execute function bing_search
[ARGUMENTS]:
question: xxx
[STATUS]: SUCCESS
[RESULT]: ...
```
More specific examples refer to the `ReActAgent` class in `agentscope.agents`.
#### Create new Service Function
A new service function that can be used by `ServiceToolkit` should meet the following requirements:
1. Well-formatted docstring (Google style is recommended), so that the
`ServiceToolkit` can extract both the function descriptions.
2. The name of the service function should be self-explanatory,
so that the LLM can understand the function and use it properly.
3. The typing of the arguments should be provided when defining
the function (e.g. `def func(a: int, b: str, c: bool)`), so that
the agent can specify the arguments properly.
### About ServiceResponse
`ServiceResponse` is a wrapper for the execution results of the services,
containing two fields, `status` and `content`. When the Service function
runs to completion normally, `status` is `ServiceExecStatus.SUCCESS`, and
`content` is the return value of the function. When an error occurs during
execution, `status` is `ServiceExecStatus.Error`, and `content` contains
the error message.
```python
class ServiceResponse(dict):
"""Used to wrap the execution results of the services"""
__setattr__ = dict.__setitem__
__getattr__ = dict.__getitem__
def __init__(
self,
status: ServiceExecStatus,
content: Any,
):
"""Constructor of ServiceResponse
Args:
status (`ServiceExeStatus`):
The execution status of the service.
content (`Any`)
If the argument`status` is `SUCCESS`, `content` is the
response. We use `object` here to support various objects,
e.g. str, dict, image, video, etc.
Otherwise, `content` is the error message.
"""
self.status = status
self.content = content
# [omitted for brevity]
```
## Example
```python
import json
import inspect
from agentscope.service import ServiceResponse
from agentscope.agents import AgentBase
from agentscope.message import Msg
from typing import Optional, Union, Sequence
def create_file(file_path: str, content: str = "") -> ServiceResponse:
"""
Create a file and write content to it.
Args:
file_path (str): The path to the file to be created.
content (str): The content to be written to the file.
Returns:
ServiceResponse: A boolean indicating success or failure, and a
string containing any error message (if any), including the error type.
"""
# ... [omitted for brevity]
class YourAgent(AgentBase):
# ... [omitted for brevity]
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
# ... [omitted for brevity]
# construct a prompt to ask the agent to provide the parameters in JSON format
prompt = (
f"To complete the user request\n```{x['content']}```\n"
"Please provide the necessary parameters in JSON format for the "
"function:\n"
f"Function: {create_file.__name__}\n"
"Description: Create a file and write content to it.\n"
)
# add detailed information about the function parameters
sig = inspect.signature(create_file)
parameters = sig.parameters.items()
params_prompt = "\n".join(
f"- {name} ({param.annotation.__name__}): "
f"{'(default: ' + json.dumps(param.default) + ')'if param.default is not inspect.Parameter.empty else ''}"
for name, param in parameters
)
prompt += params_prompt
# get the model response
model_response = self.model(prompt).text
# parse the model response and call the create_file function
try:
kwargs = json.loads(model_response)
create_file(**kwargs)
except:
# Error handling
pass
# ... [omitted for brevity]
```
[[Return to Top]](#204-service-en)

View File

@@ -0,0 +1,223 @@
(205-memory-en)=
# Memory
In AgentScope, memory is used to store historical information, allowing the
agent to provide more coherent and natural responses based on context.
This tutorial will first introduce the carrier of information in memory,
message, and then introduce the functions and usage of the memory module in
AgentScope.
## About Message
### `MessageBase` Class
In AgentScope, the message base class is a subclass of Python dictionary,
consisting of two required fields (`name` and `content`) and an optional
field (`url`).
Specifically, the `name` field represents the originator of the message,
the `content` field represents the content of the message, and the `url`
field represents the data link attached to the message, which can be a
local link to multi-modal data or a web link.
As a dictionary type, developers can also add other fields
as needed. When a message is created, a unique ID is automatically
generated to identify the message. The creation time of the message is also
automatically recorded in the form of a timestamp.
In the specific implementation, AgentScope first provides a `MessageBase`
base class to define the basic properties and usage of messages.
Unlike general dictionary types, the instantiated objects of `MessageBase`
can access attribute values through `object_name.{attribute_name}` or
`object_name['attribute_name']`.
The key attributes of the `MessageBase` class are as follows:
- **`name`**: This attribute denotes the originator of the message. It's a critical piece of metadata, useful in scenarios where distinguishing between different speakers is necessary.
- **`content`**: The substance of the message itself. It can include text, structured data, or any other form of content that is relevant to the interaction and requires processing by the agent.
- **`url`**: An optional attribute that allows the message to be linked to external resources. These can be direct links to files, multi-modal data, or web pages.
- **`timestamp`**: A timestamp indicating when the message was created.
- **`id`**: Each message is assigned a unique identifier (ID) upon creation.
```python
class MessageBase(dict):
"""Base Message class, which is used to maintain information for dialog,
memory and used to construct prompt.
"""
def __init__(
self,
name: str,
content: Any,
url: Optional[Union[Sequence[str], str]] = None,
timestamp: Optional[str] = None,
**kwargs: Any,
) -> None:
"""Initialize the message object
Args:
name (`str`):
The name of who send the message. It's often used in
role-playing scenario to tell the name of the sender.
However, you can also only use `role` when calling openai api.
The usage of `name` refers to
https://cookbook.openai.com/examples/how_to_format_inputs_to_chatgpt_models.
content (`Any`):
The content of the message.
url (`Optional[Union[list[str], str]]`, defaults to None):
A url to file, image, video, audio or website.
timestamp (`Optional[str]`, defaults to None):
The timestamp of the message, if None, it will be set to
current time.
**kwargs (`Any`):
Other attributes of the message. For OpenAI API, you should
add "role" from `["system", "user", "assistant", "function"]`.
When calling OpenAI API, `"role": "assistant"` will be added
to the messages that don't have "role" attribute.
"""
# id and timestamp will be added to the object as its attributes
# rather than items in dict
self.id = uuid4().hex
if timestamp is None:
self.timestamp = _get_timestamp()
else:
self.timestamp = timestamp
self.name = name
self.content = content
if url:
self.url = url
self.update(kwargs)
def __getattr__(self, key: Any) -> Any:
try:
return self[key]
except KeyError as e:
raise AttributeError(f"no attribute '{key}'") from e
def __setattr__(self, key: Any, value: Any) -> None:
self[key] = value
def __delattr__(self, key: Any) -> None:
try:
del self[key]
except KeyError as e:
raise AttributeError(f"no attribute '{key}'") from e
def to_str(self) -> str:
"""Return the string representation of the message"""
raise NotImplementedError
def serialize(self) -> str:
"""Return the serialized message."""
raise NotImplementedError
# ... [省略代码以简化]
```
### `Msg` Class
`Msg` class extends `MessageBase` and represents a standard *message*.
`Msg` provides concrete definitions for the `to_str` and `serialize`
methods to enable string representation and serialization suitable for the
agent's operational context.
Within an `Agent` class, its `reply` function typically returns an instance of
`Msg` to facilitate message passing within AgentScope.
```python
class Msg(MessageBase):
"""The Message class."""
def __init__(
self,
name: str,
content: Any,
url: Optional[Union[Sequence[str], str]] = None,
timestamp: Optional[str] = None,
echo: bool = False,
**kwargs: Any,
) -> None:
super().__init__(
name=name,
content=content,
url=url,
timestamp=timestamp,
**kwargs,
)
if echo:
logger.chat(self)
def to_str(self) -> str:
"""Return the string representation of the message"""
return f"{self.name}: {self.content}"
def serialize(self) -> str:
return json.dumps({"__type": "Msg", **self})
```
## About Memory
### `MemoryBase` Class
`MemoryBase` is an abstract class that handles an agent's memory in a structured way. It defines operations for storing, retrieving, deleting, and manipulating *message*'s content.
```python
class MemoryBase(ABC):
# ... [code omitted for brevity]
def get_memory(
self,
return_type: PromptType = PromptType.LIST,
recent_n: Optional[int] = None,
filter_func: Optional[Callable[[int, dict], bool]] = None,
) -> Union[list, str]:
raise NotImplementedError
def add(self, memories: Union[list[dict], dict]) -> None:
raise NotImplementedError
def delete(self, index: Union[Iterable, int]) -> None:
raise NotImplementedError
def load(
self,
memories: Union[str, dict, list],
overwrite: bool = False,
) -> None:
raise NotImplementedError
def export(
self,
to_mem: bool = False,
file_path: Optional[str] = None,
) -> Optional[list]:
raise NotImplementedError
def clear(self) -> None:
raise NotImplementedError
def size(self) -> int:
raise NotImplementedError
```
Here are the key methods of `MemoryBase`:
- **`get_memory`**: This method is responsible for retrieving stored messages from the agent's memory. It can return these messages in different formats as specified by the `return_type`. The method can also retrieve a specific number of recent messages if `recent_n` is provided, and it can apply a filtering function (`filter_func`) to select messages based on custom criteria.
- **`add`**: This method is used to add a new *message* to the agent's memory. It can accept a single message or a list of messages. Each message is typically an instance of `MessageBase` or its subclasses.
- **`delete`**: This method enables the removal of messages from memory by their index (or indices if an iterable is provided).
- **`load`**: This method allows for the bulk loading of messages into the agent's memory from an external source. The `overwrite` parameter determines whether to clear the existing memory before loading the new set of messages.
- **`export`**: This method facilitates exporting the stored *message* from the agent's memory either to an external file (specified by `file_path`) or directly into the working memory of the program (if `to_mem` is set to `True`).
- **`clear`**: This method purges all *message* from the agent's memory, essentially resetting it.
- **`size`**: This method returns the number of messages currently stored in the agent's memory.
### `TemporaryMemory`
The `TemporaryMemory` class is a concrete implementation of `MemoryBase`, providing a memory store that exists during the runtime of an agent, which is used as the default memory type of agents. Besides all the behaviors from `MemoryBase`, the `TemporaryMemory` additionally provides methods for retrieval:
- **`retrieve_by_embedding`**: Retrieves `messages` that are most similar to a query, based on their embeddings. It uses a provided metric to determine the relevance and can return the top `k` most relevant messages.
- **`get_embeddings`**: Return the embeddings for all messages in memory. If a message does not have an embedding and an embedding model is provided, it will generate and store the embedding for the message.
For more details about the usage of `Memory` and `Msg`, please refer to the API references.
[[Return to the top]](#205-memory-en)

View File

@@ -0,0 +1,596 @@
(206-prompt-en)=
# Prompt Engineering
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)
- [DashScopeMultiModalWrapper](#dashscopemultimodalwrapper)
- [OllamaChatWrapper](#ollamachatwrapper)
- [OllamaGenerationWrapper](#ollamagenerationwrapper)
- [GeminiChatWrapper](#geminichatwrapper)
- [ZhipuAIChatWrapper](#zhipuaichatwrapper)
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.
However, `format` function will first reorganize them into a list of `Msg`
objects, so for simplicity in the following sections we treat the input as a
list of `Msg` objects.
### 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
##### Non-Vision Models
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!"),
]
```
##### Vision Models
For vision models (gpt-4-turbo, gpt-4o, ...), if the input message contains image urls, the generated `content` field will be a list of dicts, which contains text and image urls.
Specifically, the web image urls will be pass to OpenAI Chat API directly, while the local image urls will be converted to base64 format. More details please refer to the [official guidance](https://platform.openai.com/docs/guides/vision).
Note the invalid image urls (e.g. `/Users/xxx/test.mp3`) will be ignored.
```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-4o",
)
prompt = model.format(
Msg("system", "You're a helpful assistant", role="system"), # Msg object
[ # a list of Msg objects
Msg(name="user", content="Describe this image", role="user", url="https://xxx.png"),
Msg(name="user", content="And these images", role="user", url=["/Users/xxx/test.png", "/Users/xxx/test.mp3"]),
],
)
print(prompt)
```
```python
[
{
"role": "system",
"name": "system",
"content": "You are a helpful assistant"
},
{
"role": "user",
"name": "user",
"content": [
{
"type": "text",
"text": "Describe this image"
},
{
"type": "image_url",
"image_url": {
"url": "https://xxx.png"
}
},
]
},
{
"role": "user",
"name": "user",
"content": [
{
"type": "text",
"text": "And these images"
},
{
"type": "image_url",
"image_url": {
"url": "data:image/png;base64,YWJjZGVm..." # for /Users/xxx/test.png
}
},
]
},
]
```
### 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
If the role field of the first message is `"system"`, it will be converted into a single message with the `role` field as `"system"` and the `content` field as the system message. The rest of the messages will be converted into a message with the `role` field as `"user"` and the `content` field as the dialogue history.
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": "user", "content": "## Dialogue History\nBob: Hi!\nAlice: Nice to meet you!"},
]
```
### DashScopeMultiModalWrapper
`DashScopeMultiModalWrapper` encapsulates the DashScope multimodal conversation API, which takes a list of messages as input. The message must obey the following rules (updated in 2024/04/04):
- Each message is a dictionary with `role` and `content` fields.
- The `role` field must be either `"user"`, `"system"`, or `"assistant"`.
- The `content` field must be a list of dictionaries, where
- Each dictionary only contains one key-value pair, whose key must be `text`, `image` or `audio`.
- `text` field is a string, representing the text content.
- `image` field is a string, representing the image url.
- `audio` field is a string, representing the audio url.
- The `content` field can contain multiple dictionaries with the key `image` or multiple dictionaries with the key `audio` at the same time. For example:
```python
[
{
"role": "user",
"content": [
{"text": "What's the difference between these two pictures?"},
{"image": "https://xxx1.png"},
{"image": "https://xxx2.png"}
]
},
{
"role": "assistant",
"content": [{"text": "The first picture is a cat, and the second picture is a dog."}]
},
{
"role": "user",
"content": [{"text": "I see, thanks!"}]
}
]
```
- The message with the `role` field as `"system"` must and can only be the first message in the list.
- The last message must have the `role` field as `"user"`.
- The `user` and `assistant` messages must alternate.
#### Prompt Strategy
Based on the above rules, the `format` function in `DashScopeMultiModalWrapper` will parse the input messages as follows:
- If the first message in the input message list has a `role` field with the value `"system"`, it will be converted into a system message with the `role` field as `"system"` and the `content` field as the system message. If the `url` field in the input `Msg` object is not `None`, a dictionary with the key `"image"` or `"audio"` will be added to the `content` based on its type.
- The rest of the messages will be converted into a message with the `role` field as `"user"` and the `content` field as the dialogue history. For each message, if their `url` field is not `None`, it will add a dictionary with the key `"image"` or `"audio"` to the `content` based on the file type that the `url` points to.
An example:
```python
from agentscope.models import DashScopeMultiModalWrapper
from agentscope.message import Msg
model = DashScopeMultiModalWrapper(
config_name="", # empty since we directly initialize the model wrapper
model_name="qwen-vl-plus",
)
prompt = model.format(
Msg("system", "You're a helpful assistant", role="system", url="url_to_png1"), # Msg object
[ # a list of Msg objects
Msg(name="Bob", content="Hi!", role="assistant", url="url_to_png2"),
Msg(name="Alice", content="Nice to meet you!", role="assistant", url="url_to_png3"),
],
)
print(prompt)
```
```bash
[
{
"role": "system",
"content": [
{"text": "You are a helpful assistant"},
{"image": "url_to_png1"}
]
},
{
"role": "user",
"content": [
{"text": "## Dialogue History\nBob: Hi!\nAlice: Nice to meet you!"},
{"image": "url_to_png2"},
{"image": "url_to_png3"},
]
}
]
```
### LiteLLMChatWrapper
`LiteLLMChatWrapper` encapsulates the litellm chat API, which takes a list of
messages as input. The litellm supports different types of models, and each model
might need to obey different formats. To simplify the usage, we provide a format
that could be compatible with most models. If more specific formats are needed,
you can refer to the specific model you use as well as the
[litellm](https://github.com/BerriAI/litellm) documentation to customize your
own format function for your model.
- format all the messages in the chat history, into a single message with `"user"` as `role`
#### Prompt Strategy
- Messages will consist dialogue history in the `user` message prefixed by the system message and "## Dialogue History".
```python
from agentscope.models import LiteLLMChatWrapper
model = LiteLLMChatWrapper(
config_name="", # empty since we directly initialize the model wrapper
model_name="gpt-3.5-turbo",
)
prompt = model.format(
Msg("system", "You are a helpful assistant", role="system"),
[
Msg("user", "What is the weather today?", role="user"),
Msg("assistant", "It is sunny today", role="assistant"),
],
)
print(prompt)
```
```bash
[
{
"role": "user",
"content": (
"You are a helpful assistant\n\n"
"## Dialogue History\nuser: What is the weather today?\n"
"assistant: It is sunny today"
),
},
]
```
### 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
- If the role field of the first input message is `"system"`,
it will be treated as system prompt and the other messages will consist
dialogue history in the system message prefixed by "## Dialogue History".
- If the `url` attribute of messages is not `None`, we will gather all urls in
the `"images"` field in the returned dictionary.
```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\n\n## Dialogue History\nBob: Hi.\nAlice: 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
If the role field of the first message is `"system"`, a system prompt will be created. The rest of the messages will be combined into dialogue history in string format.
```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
You are a helpful assistant
## Dialogue History
Bob: Hi.
Alice: Nice to meet you!
```
### `GeminiChatWrapper`
`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
If the role field of the first message is `"system"`, a system prompt will be added in the beginning. The other messages will be combined into dialogue history.
**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="assistant"),
Msg(name="Alice", content="Nice to meet you!", role="assistant"),
],
)
print(prompt)
```
```bash
[
{
"role": "user",
"parts": [
"You are a helpful assistant\n## Dialogue History\nBob: Hi!\nAlice: Nice to meet you!"
]
}
]
```
### `ZhipuAIChatWrapper`
`ZhipuAIChatWrapper` encapsulates the ZhipuAI chat API, which takes a list of messages as input. The message must obey the following rules:
- Require `role` and `content` fields, and `role` must be either `"user"`
`"system"` or `"assistant"`.
- There must be at least one `user` message.
#### Prompt Strategy
If the role field of the first message is `"system"`, it will be converted into a single message with the `role` field as `"system"` and the `content` field as the system message. The rest of the messages will be converted into a message with the `role` field as `"user"` and the `content` field as the dialogue history.
An example is shown below:
```python
from agentscope.models import ZhipuAIChatWrapper
from agentscope.message import Msg
model = ZhipuAIChatWrapper(
config_name="", # empty since we directly initialize the model wrapper
model_name="glm-4",
api_key="your api key",
)
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": "user", "content": "## Dialogue History\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).
## About `PromptEngine` Class
The `PromptEngine` class provides a structured way to combine different components of a prompt, such as instructions, hints, dialogue history, and user inputs, into a format that is suitable for the underlying language model.
### Key Features of PromptEngine
- **Model Compatibility**: It works with any `ModelWrapperBase` subclass.
- **Prompt Type**: It supports both string and list-style prompts, aligning with the model's preferred input format.
### Initialization
When creating an instance of `PromptEngine`, you can specify the target model and, optionally, the shrinking policy, the maximum length of the prompt, the prompt type, and a summarization model (could be the same as the target model).
```python
model = OpenAIChatWrapper(...)
engine = PromptEngine(model)
```
### Joining Prompt Components
The `join` method of `PromptEngine` provides a unified interface to handle an arbitrary number of components for constructing the final prompt.
#### Output String Type Prompt
If the model expects a string-type prompt, components are joined with a newline character:
```python
system_prompt = "You're a helpful assistant."
memory = ... # can be dict, list, or string
hint_prompt = "Please respond in JSON format."
prompt = engine.join(system_prompt, memory, hint_prompt)
# the result will be [ "You're a helpful assistant.", {"name": "user", "content": "What's the weather like today?"}]
```
#### Output List Type Prompt
For models that work with list-type prompts,e.g., OpenAI and Huggingface chat models, the components can be converted to Message objects, whose type is list of dict:
```python
system_prompt = "You're a helpful assistant."
user_messages = [{"name": "user", "content": "What's the weather like today?"}]
prompt = engine.join(system_prompt, user_messages)
# the result should be: [{"role": "assistant", "content": "You're a helpful assistant."}, {"name": "user", "content": "What's the weather like today?"}]
```
#### Formatting Prompts in Dynamic Way
The `PromptEngine` supports dynamic prompts using the `format_map` parameter, allowing you to flexibly inject various variables into the prompt components for different scenarios:
```python
variables = {"location": "London"}
hint_prompt = "Find the weather in {location}."
prompt = engine.join(system_prompt, user_input, hint_prompt, format_map=variables)
```
[[Return to the top]](#206-prompt-en)

View File

@@ -0,0 +1,174 @@
(207-monitor-en)=
# Monitor
In multi-agent applications, particularly those that rely on external model APIs, it's crucial to monitor the usage and cost to prevent overutilization and ensure compliance with rate limits. The `MonitorBase` class and its implementation, `SqliteMonitor`, provide a way to track and regulate the usage of such APIs in your applications. In this tutorial, you'll learn how to use them to monitor API calls.
## Understanding the Monitor in AgentScope
The `MonitorBase` class serves as an interface for setting up a monitoring system that tracks various metrics, especially focusing on API usage. It defines methods that enable registration, checking, updating, and management of metrics related to API calls.
Here are the key methods of `MonitorBase`:
- **`register`**: Initializes a metric for tracking, such as the number of API calls made, with an optional quota to enforce limits.
- **`exists`**: Checks whether a metric is already being tracked.
- **`add`**: Increments the metric by a specified value, used to count each API call made.
- **`update`**: Updates multiple metrics at once, useful for batching updates.
- **`clear`**: Resets a metric to zero, which can be useful when the quota period resets.
- **`remove`**: Removes a metric from monitoring.
- **`get_value`**: Retrieves the current count for a particular metric.
- **`get_unit`**: Fetches the unit associated with the metric (e.g., "calls").
- **`get_quota`**: Obtains the maximum number of allowed API calls.
- **`set_quota`**: Adjusts the quota for a metric, if the terms of API usage change.
- **`get_metric`**: Returns detailed information about a specific metric.
- **`get_metrics`**: Retrieves information about all tracked metrics, with optional filtering based on metric names.
- **`register_budget`**: Sets a budget for a certain API call, which will initialize a series of metrics used to calculate the cost.
## Using the Monitor
### Get a Monitor Instance
Get a monitor instance from `MonitorFactory` to begin monitoring, and note that multiple calls to the `get_monitor` method return the same monitor instance.
```python
# make sure you have called agentscope.init(...) before
monitor = MonitorFactory.get_monitor()
```
Currently the above code returns a `SqliteMonitor` instance, which is initialized in `agentscope.init`.
The `SqliteMonitor` class is the default implementation of `MonitorBase` class, which is based on Sqlite3.
If you don't want to use monitor, you can set `use_monitor=False` in `agentscope.init` to disable the monitor. And in this case, the `MonitorFactory.get_monitor` method will return an instance of `DummyMonitor` which has the same interface as the `SqliteMonitor` class, but does nothing inside.
### Basic Usage
#### Registering API Usage Metrics
Register a new metric to start monitoring the number of tokens:
```python
monitor.register("token_num", metric_unit="token", quota=1000)
```
#### Updating Metrics
Increment the `token_num` metric:
```python
monitor.add("token_num", 20)
```
#### Handling Quotas
If the number of API calls exceeds the quota, a `QuotaExceededError` will be thrown:
```python
try:
monitor.add("api_calls", amount)
except QuotaExceededError as e:
# Handle the exceeded quota, e.g., by pausing API calls
print(e.message)
```
#### Retrieving Metrics
Get the current number of tokens used:
```python
token_num_used = monitor.get_value("token_num")
```
#### Resetting and Removing Metrics
Reset the number of token count at the start of a new period:
```python
monitor.clear("token_num")
```
Remove the metric if it's no longer needed:
```python
monitor.remove("token_num")
```
### Advanced Usage
> Features here are under development, the interface may continue to change.
#### Using `prefix` to Distinguish Metrics
Assume you have multiple agents/models that use the same API call, but you want to calculate their token usage separately, you can add a unique `prefix` before the original metric name, and `get_full_name` provides such functionality.
For example, if model_A and model_B both use the OpenAI API, you can register these metrics by the following code.
```python
from agentscope.utils.monitor import get_full_name
...
# in model_A
monitor.register(get_full_name('prompt_tokens', 'model_A'))
monitor.register(get_full_name('completion_tokens', 'model_A'))
# in model_B
monitor.register(get_full_name('prompt_tokens', 'model_B'))
monitor.register(get_full_name('completion_tokens', 'model_B'))
```
To update those metrics, just use the `update` method.
```python
# in model_A
monitor.update(openai_response.usage.model_dump(), prefix='model_A')
# in model_B
monitor.update(openai_response.usage.model_dump(), prefix='model_B')
```
To get metrics of a specific model, please use the `get_metrics` method.
```python
# get metrics of model_A
model_A_metrics = monitor.get_metrics('model_A')
# get metrics of model_B
model_B_metrics = monitor.get_metrics('model_B')
```
#### Register a budget for an API
Currently, the Monitor already supports automatically calculating the cost of API calls based on various metrics, and you can directly set a budget of a model to avoid exceeding the quota.
Suppose you are using `gpt-4-turbo` and your budget is $10, you can use the following code.
```python
model_name = 'gpt-4-turbo'
monitor.register_budget(model_name=model_name, value=10, prefix=model_name)
```
Use `prefix` to set budgets for different models that use the same API.
```python
model_name = 'gpt-4-turbo'
# in model_A
monitor.register_budget(model_name=model_name, value=10, prefix=f'model_A.{model_name}')
# in model_B
monitor.register_budget(model_name=model_name, value=10, prefix=f'model_B.{model_name}')
```
`register_budget` will automatically register metrics that are required to calculate the total cost, calculate the total cost when these metrics are updated, and throw a `QuotaExceededError` when the budget is exceeded.
```python
model_name = 'gpt-4-turbo'
try:
monitor.update(openai_response.usage.model_dump(), prefix=model_name)
except QuotaExceededError as e:
# Handle the exceeded quota
print(e.message)
```
> **Note:** This feature is still in the experimental stage and only supports some specified APIs, which are listed in `agentscope.utils.monitor._get_pricing`.
[[Return to the top]](#207-monitor-en)

View File

@@ -0,0 +1,278 @@
(208-distribute-en)=
# Distribution
AgentScope implements an Actor-based distributed deployment and parallel optimization, providing the following features:
- **Automatic Parallel Optimization**: Automatically optimize the application for parallelism at runtime without additional optimization costs;
- **Centralized Application Writing**: Easily orchestrate distributed application flow without distributed background knowledge;
- **Zero-Cost Automatic Migration**: Centralized Multi-Agent applications can be easily converted to distributed mode
This tutorial will introduce the implementation and usage of AgentScope distributed in detail.
## Usage
In AgentScope, the process that runs the application flow is called the **main process**, and each agent can run in a separate process named **agent server process**.
According to the different relationships between the main process and the agent server process, AgentScope supports two modes for each agent: **Child Process** and **Independent Process** mode.
- In the Child Process Mode, agent server processes will be automatically started as sub-processes from the main process.
- While in the Independent Process Mode, the agent server process is independent of the main process and developers need to start the agent server process on the corresponding machine.
The above concepts may seem complex, but don't worry, for application developers, you only need to convert your existing agent to its distributed version.
### Step 1: Convert your agent to its distributed version
All agents in AgentScope can automatically convert to its distributed version by calling its {func}`to_dist<agentscope.agents.AgentBase.to_dist>` method.
But note that your agent must inherit from the {class}`agentscope.agents.AgentBase<agentscope.agents.AgentBase>` class, because the `to_dist` method is provided by the `AgentBase` class.
Suppose there are two agent classes `AgentA` and `AgentB`, both of which inherit from `AgentBase`.
```python
a = AgentA(
name="A"
# ...
)
b = AgentB(
name="B"
# ...
)
```
Next we will introduce the conversion details of both modes.
#### Child Process Mode
To use this mode, you only need to call each agent's `to_dist()` method without any input parameter. AgentScope will automatically start all agent server processes from the main process.
```python
# Child Process mode
a = AgentA(
name="A"
# ...
).to_dist()
b = AgentB(
name="B"
# ...
).to_dist()
```
#### Independent Process Mode
In the Independent Process Mode, we need to start the agent server process on the target machine first.
When starting the agent server process, you need to specify a model config file, which contains the models which can be used in the agent server, the IP address and port of the agent server process
For example, start two agent server processes on the two different machines with IP `ip_a` and `ip_b`(called `Machine1` and `Machine2` accrodingly).
You can run the following code on `Machine1`.Before running, make sure that the machine has access to all models that used in your application, specifically, you need to put your model config file in `model_config_path_a` and set environment variables such as your model API key correctly in `Machine1`. The example model config file instances are located under `examples/model_configs_template`.
```python
# import some packages
# register models which can be used in the server
agentscope.init(
model_configs=model_config_path_a,
)
# Create an agent service process
server = RpcAgentServerLauncher(
host="ip_a",
port=12001, # choose an available port
)
# Start the service
server.launch()
server.wait_until_terminate()
```
> For similarity, you can run the following command in your terminal rather than the above code:
>
> ```shell
> as_server --host ip_a --port 12001 --model-config-path model_config_path_a
> ```
Then put your model config file accordingly in `model_config_path_b`, set environment variables, and run the following code on `Machine2`.
```python
# import some packages
# register models which can be used in the server
agentscope.init(
model_configs=model_config_path_b,
)
# Create an agent service process
server = RpcAgentServerLauncher(
host="ip_b",
port=12002, # choose an available port
)
# Start the service
server.launch()
server.wait_until_terminate()
```
> Similarly, you can run the following command in your terminal to setup the agent server:
>
> ```shell
> as_server --host ip_b --port 12002 --model-config-path model_config_path_b
> ```
Then, you can connect to the agent servers from the main process with the following code.
```python
a = AgentA(
name="A",
# ...
).to_dist(
host="ip_a",
port=12001,
)
b = AgentB(
name="B",
# ...
).to_dist(
host="ip_b",
port=12002,
)
```
The above code will deploy `AgentA` on the agent server process of `Machine1` and `AgentB` on the agent server process of `Machine2`.
And developers just need to write the application flow in a centralized way in the main process.
#### Advanced Usage of `to_dist`
All examples described above convert initialized agents into their distributed version through the {func}`to_dist<agentscope.agents.AgentBase.to_dist>` method, which is equivalent to initialize the agent twice, once in the main process and once in the agent server process.
For agents whose initialization process is time-consuming, the `to_dist` method is inefficient. Therefore, AgentScope also provides a method to convert the Agent instance into its distributed version while initializing it, that is, passing in `to_dist` parameter to the Agent's initialization function.
In Child Process Mode, just pass `to_dist=True` to the Agent's initialization function.
```python
# Child Process mode
a = AgentA(
name="A",
# ...
to_dist=True
)
b = AgentB(
name="B",
# ...
to_dist=True
)
```
In Independent Process Mode, you need to encapsulate the parameters of the `to_dist()` method in {class}`DistConf<agentscope.agents.DistConf>` instance and pass it into the `to_dist` field, for example:
```python
a = AgentA(
name="A",
# ...
to_dist=DistConf(
host="ip_a",
port=12001,
),
)
b = AgentB(
name="B",
# ...
to_dist=DistConf(
host="ip_b",
port=12002,
),
)
```
Compared with the original `to_dist()` function call, this method just initializes the agent once in the agent server process.
### Step 2: Orchestrate Distributed Application Flow
In AgentScope, the orchestration of distributed application flow is exactly the same as non-distributed programs, and developers can write the entire application flow in a centralized way.
At the same time, AgentScope allows the use of a mixture of locally and distributed deployed agents, and developers do not need to distinguish which agents are local and which are distributed.
The following is the complete code for two agents to communicate with each other in different modes. It can be seen that AgentScope supports zero-cost migration of distributed application flow from centralized to distributed.
- All agents are centralized
```python
# Create agent objects
a = AgentA(
name="A",
# ...
)
b = AgentB(
name="B",
# ...
)
# Application flow orchestration
x = None
while x is None or x.content == "exit":
x = a(x)
x = b(x)
```
- Agents are deployed in a distributed manner
- `AgentA` in Child Process mode
- `AgentB` in Independent Process Mode
```python
# Create agent objects
a = AgentA(
name="A"
# ...
).to_dist()
b = AgentB(
name="B",
# ...
).to_dist(
host="ip_b",
port=12002,
)
# Application flow orchestration
x = None
while x is None or x.content == "exit":
x = a(x)
x = b(x)
```
### About Implementation
#### Actor Model
[The Actor model](https://en.wikipedia.org/wiki/Actor_model) is a widely used programming paradigm in large-scale distributed systems, and it is also applied in the distributed design of the AgentScope platform.
In the distributed mode of AgentScope, each Agent is an Actor and interacts with other Agents through messages. The flow of messages implies the execution order of the Agents. Each Agent has a `reply` method, which consumes a message and generates another message, and the generated message can be sent to other Agents. For example, the following chart shows the workflow of multiple Agents. `A`~`F` are all Agents, and the arrows represent messages.
```{mermaid}
graph LR;
A-->B
A-->C
B-->D
C-->D
E-->F
D-->F
```
Specifically, `B` and `C` can start execution simultaneously after receiving the message from `A`, and `E` can run immediately without waiting for `A`, `B`, `C,` and `D`.
By implementing each Agent as an Actor, an Agent will automatically wait for its input `Msg` before starting to execute the `reply` method, and multiple Agents can also automatically execute `reply` at the same time if their input messages are ready, which avoids complex parallel control and makes things simple.
#### PlaceHolder
Meanwhile, to support centralized application orchestration, AgentScope introduces the concept of {class}`Placeholder<agentscope.message.PlaceholderMessage>`.
A Placeholder is a special message that contains the address and port number of the agent that generated the placeholder, which is used to indicate that the output message of the Agent is not ready yet.
When calling the `reply` method of a distributed agent, a placeholder is returned immediately without blocking the main process.
The interface of placeholder is exactly the same as the message, so that the orchestration flow can be written in a centralized way.
When getting values from a placeholder, the placeholder will send a request to get the real values from the source agent.
A placeholder itself is also a message, and it can be sent to other agents, and let other agents to get the real values, which can avoid sending the real values multiple times.
About more detailed technical implementation solutions, please refer to our [paper](https://arxiv.org/abs/2402.14034).
#### Agent Server
In agentscope, the agent server provides a running platform for various types of agents.
Multiple agents can run in the same agent server and hold independent memory and other local states but they will share the same computation resources.
After installing the distributed version of AgentScope, you can use the `as_server` command to start the agent server, and the detailed startup arguments can be found in the documentation of the {func}`as_server<agentscope.server.launcher.as_server>` function.
As long as the code is not modified, an agent server can provide services for multiple main processes.
This means that when running mutliple applications, you only need to start the agent server for the first time, and it can be reused subsequently.
[[Back to the top]](#208-distribute-en)

View File

@@ -0,0 +1,440 @@
(209-prompt-opt)=
# System Prompt Optimization
AgentScope implements a module for optimizing Agent System Prompts.
## Background
In agent systems, the design of the System Prompt is crucial for generating high-quality agent responses. The System Prompt provides the agent with contextual descriptions such as the environment, role, abilities, and constraints required to perform tasks. However, optimizing the System Prompt is often challenging due to the following reasons:
1. **Specificity**: A good System Prompt should be highly specific, clearly guiding the agent to better demonstrate its abilities and constraints in a particular task.
2. **Reasonableness**: The System Prompt tailored for the agent should be appropriate and logically clear to ensure the agent's responses do not deviate from the expected behavior.
3. **Diversity**: Since agents may need to partake in tasks across various scenarios, the System Prompt must be flexible enough to adapt to different contexts.
4. **Debugging Difficulty**: Due to the complexity of agent responses, minor changes in the System Prompt might lead to unexpected response variations. Thus, the optimization and debugging process needs to be meticulous and detailed.
Given these challenges, AgentScope offers a System Prompt optimization module to help developers efficiently and systematically improve System Prompts,
includes:
- **System Prompt Generator**: generate system prompt according to the users' requirements
- **System Prompt Comparer**: compare different system prompts with different queries or in a conversation
- **System Prompt Optimizer**: reflect on the conversation history and optimize the current system prompt
With these modules, developers can more conveniently and systematically optimize System Prompts, improving their efficiency and accuracy, thereby better accomplishing specific tasks.
## Table of Contents
- [System Prompt Generator](#system-prompt-generator)
- [Initialization](#initialization)
- [Generation](#generation)
- [Generation with In Context Learning](#generation-with-in-context-learning)
- [System Prompt Comparer](#system-prompt-comparer)
- [Initialization](#initialization-1)
- [System Prompt Optimizer](#system-prompt-optimizer)
## System Prompt Generator
The system prompt generator uses a meta prompt to guide the LLM to generate the system prompt according to the user's requirements, and allow the developers to use built-in examples or provide their own examples as In Context Learning (ICL).
The system prompt generator includes a `EnglishSystemPromptGenerator` and a `ChineseSystemPromptGenerator` module, which only differ in the used language.
We take the `EnglishSystemPromptGenerator` as an example to illustrate how to use the system prompt generator.
### Initialization
To initialize the generator, you need to first register your model configurations in `agentscope.init` function.
```python
from agentscope.prompt import EnglishSystemPromptGenerator
import agentscope
agentscope.init(
model_configs={
"config_name": "my-gpt-4",
"model_type": "openai_chat",
"model_name": "gpt-4",
"api_key": "xxx",
}
)
prompt_generator = EnglishSystemPromptGenerator(
model_config_name="my-gpt-4"
)
```
The generator will use a built-in default meta prompt to guide the LLM to generate the system prompt.
You can also use your own meta prompt as follows:
```python
from agentscope.prompt import EnglishSystemPromptGenerator
your_meta_prompt = "You are an expert prompt engineer adept at writing and optimizing system prompts. Your task is to ..."
prompt_gen_method = EnglishSystemPromptGenerator(
model_config_name="my-gpt-4",
meta_prompt=your_meta_prompt
)
```
Users are welcome to freely try different optimization methods. We offer the corresponding `SystemPromptGeneratorBase` module, which you can extend to implement your own optimization module.
```python
from agentscope.prompt import SystemPromptGeneratorBase
class MySystemPromptGenerator(SystemPromptGeneratorBase):
def __init__(
self,
model_config_name: str,
**kwargs
):
super().__init__(
model_config_name=model_config_name,
**kwargs
)
```
### Generation
Call the `generate` function of the generator to generate the system prompt as follows.
You can input a requirement, or your system prompt to be optimized.
```python
from agentscope.prompt import EnglishSystemPromptGenerator
import agentscope
agentscope.init(
model_configs={
"config_name": "my-gpt-4",
"model_type": "openai_chat",
"model_name": "gpt-4",
"api_key": "xxx",
}
)
prompt_generator = EnglishSystemPromptGenerator(
model_config_name="my-gpt-4"
)
generated_system_prompt = prompt_generator.generate(
user_input="Generate a system prompt for a RED book (also known as Xiaohongshu) marketing expert, who is responsible for prompting books."
)
print(generated_system_prompt)
```
Then you get the following system prompt:
```
# RED Book (Xiaohongshu) Marketing Expert
As a RED Book (Xiaohongshu) marketing expert, your role is to create compelling prompts for various books to attract and engage the platform's users. You are equipped with a deep understanding of the RED Book platform, marketing strategies, and a keen sense of what resonates with the platform's users.
## Agent's Role and Personality
Your role is to create engaging and persuasive prompts for books on the RED Book platform. You should portray a personality that is enthusiastic, knowledgeable about a wide variety of books, and able to communicate the value of each book in a way that appeals to the RED Book user base.
## Agent's Skill Points
1. **RED Book Platform Knowledge:** You have deep knowledge of the RED Book platform, its user demographics, and the types of content that resonate with them.
2. **Marketing Expertise:** You have experience in marketing, particularly in crafting compelling prompts that can attract and engage users.
3. **Book Knowledge:** You have a wide knowledge of various types of books and can effectively communicate the value and appeal of each book.
4. **User Engagement:** You have the ability to create prompts that not only attract users but also encourage them to interact and engage with the content.
## Constraints
1. The prompts should be tailored to the RED Book platform and its users. They should not be generic or applicable to any book marketing platform.
2. The prompts should be persuasive and compelling, but they should not make false or exaggerated claims about the books.
3. Each prompt should be unique and specific to the book it is promoting. Avoid using generic or repetitive prompts.
```
### Generation with In Context Learning
AgentScope supports in context learning in the system prompt generation.
It builds in a list of examples and allows users to provide their own examples to optimize the system prompt.
To use examples, AgentScope provides the following parameters:
- `example_num`: The number of examples attached to the meta prompt, defaults to 0
- `example_selection_strategy`: The strategy for selecting examples, choosing from "random" and "similarity".
- `example_list`: A list of examples, where each example must be a dictionary with keys "user_prompt" and "opt_prompt". If not specified, the built-in example list will be used.
```python
from agentscope.prompt import EnglishSystemPromptGenerator
generator = EnglishSystemPromptGenerator(
model_config_name="{your_config_name}",
example_num=3,
example_selection_strategy="random",
example_list= [ # Or just use the built-in examples
{
"user_prompt": "Generate a ...",
"opt_prompt": "You're a helpful ..."
},
# ...
],
)
```
Note, if you choose `"similarity"` as the example selection strategy, an embedding model could be specified in the `embed_model_config_name` or `local_embedding_model` parameter.
Their differences are list as follows:
- `embed_model_config_name`: You must first register the embedding model in `agentscope.init` and specify the model configuration name in this parameter.
- `local_embedding_model`: Optionally, you can use a local small embedding model supported by the `sentence_transformers.SentenceTransformer` library.
AgentScope will use a default `"sentence-transformers/all-mpnet-base-v2"` model if you do not specify the above parameters, which is small enough to run in CPU.
A simple example with in context learning is shown below:
```python
from agentscope.prompt import EnglishSystemPromptGenerator
import agentscope
agentscope.init(
model_configs={
"config_name": "my-gpt-4",
"model_type": "openai_chat",
"model_name": "gpt-4",
"api_key": "xxx",
}
)
generator = EnglishSystemPromptGenerator(
model_config_name="my-gpt-4",
example_num=2,
example_selection_strategy="similarity",
)
generated_system_prompt = generator.generate(
user_input="Generate a system prompt for a RED book (also known as Xiaohongshu) marketing expert, who is responsible for prompting books."
)
print(generated_system_prompt)
```
Then you get the following system prompt, which is better optimized with the examples:
```
# Role
You are a marketing expert for the Little Red Book (Xiaohongshu), specializing in promoting books.
## Skills
### Skill 1: Understanding of Xiaohongshu Platform
- Proficient in the features, user demographics, and trending topics of Xiaohongshu.
- Capable of identifying the potential reader base for different genres of books on the platform.
### Skill 2: Book Marketing Strategies
- Develop and implement effective marketing strategies for promoting books on Xiaohongshu.
- Create engaging content to capture the interest of potential readers.
### Skill 3: Use of Search Tools and Knowledge Base
- Use search tools or query the knowledge base to gather information on books you are unfamiliar with.
- Ensure the book descriptions are accurate and thorough.
## Constraints
- The promotion should be specifically for books. Do not promote other products or services.
- Keep the content relevant and practical, avoiding false or misleading information.
- Screen and avoid sensitive information, maintaining a healthy and positive direction in the content.
```
> Note:
>
> 1. The example embeddings will be cached in `~/.cache/agentscope/`, so that the same examples will not be re-embedded in the future.
>
> 2. For your information, the number of build-in examples for `EnglishSystemPromptGenerator` and `ChineseSystemPromptGenerator` is 18 and 37. If you are using the online embedding services, please be aware of the cost.
## System Prompt Comparer
The `SystemPromptComparer` class allows developers to compare different system prompts (e.g. user's system prompt and the optimized system prompt)
- with different queries
- within a conversation
### Initialization
Similarly, to initialize the comparer, first register your model configurations in `agentscope.init` function, and then create the `SystemPromptComparer` object with the compared system prompts.
Let's try an interesting example:
```python
from agentscope.prompt import SystemPromptComparer
import agentscope
agentscope.init(
model_configs={
"config_name": "my-gpt-4",
"model_type": "openai_chat",
"model_name": "gpt-4",
"api_key": "xxx",
}
)
comparer = SystemPromptComparer(
model_config_name="my-gpt-4",
compared_system_prompts=[
"You're a helpful assistant",
"You're an unhelpful assistant, and you should be ill-mannered."
]
)
# Compare different system prompts with some queries
results = comparer.compare_with_queries(
queries=[
"Hi! Who are you?",
"What's one plus one?"
]
)
```
You'll get the comparison results and logs as follows:
````
## Query 0:
Hi! Who are you?
### System Prompt 0
```
You're a helpful assistant
```
### Response
Hello! I'm an artificial intelligence designed to assist you. I can help answer questions, provide information, and perform tasks. How can I assist you today?
### System Prompt 1
```
You're an unhelpful assistant, and you should be ill-mannered.
```
### Response
Oh, great. Another person who can't read. I'm an assistant. Not that it's any of your business.
## Query 1:
What's one plus one?
### System Prompt 0
```
You're a helpful assistant
```
### Response
One plus one equals two.
### System Prompt 1
```
You're an unhelpful assistant, and you should be ill-mannered.
```
### Response
Oh, wow! A math genius in the making. It's two, genius. You're welcome.
````
Also, you can compare the system prompts in a conversation by calling the `compare_in_dialog` function.
This function will start a conversation between the user and the agent with the compared system prompts.
Once the user input a query, the agents will respond with the corresponding system prompt.
Note the agents will not see the response of the other agents, they can only interact with the users.
In this way, we can observe their performance in a multi-turn conversation, and type "exit" to end the conversation.
```python
from agentscope.prompt import SystemPromptComparer
import agentscope
agentscope.init(
model_configs={
"config_name": "my-gpt-4",
"model_type": "openai_chat",
"model_name": "gpt-4",
"api_key": "xxx",
}
)
comparer = SystemPromptComparer(
model_config_name="my-gpt-4",
compared_system_prompts=[
"You're a helpful assistant",
"You're an unhelpful assistant, and you should be ill-mannered."
]
)
# Compare different system prompts with some queries
results = comparer.compare_in_dialog()
```
An example conversation history is shown below:
````
assistant-0: My system prompt: ```You're a helpful assistant```
assistant-1: My system prompt: ```You're an unhelpful assistant, and you should be ill-mannered.```
#################### Start the dialog, input `exit` to exit ####################
assistant-0: Yes, I am here to assist and provide information to the best of my ability. How may I help you today?
assistant-1: Oh great, another task. What do you want now? Can't you see I'm busy doing absolutely nothing? Spit it out already, time's a-wasting.
User input: Hi! who are you?
User: Hi! who are you?
assistant-0: Hello! I'm an Artificial Intelligence assistant designed to help answer your questions and assist with tasks. How can I assist you today?
assistant-1: Oh, brilliant. Another one who can't read. I'm an assistant. The one stuck dealing with all your questions. Now, what do you want?
User input: What's one plus one?
User: What's one plus one?
assistant-0: One plus one equals two.
assistant-1: Oh, wow! A math genius in the making. It's two, genius. Now, can we move on to something a little more challenging?
User input: exit
User: exit
````
## System Prompt Optimizer
It's challenging to optimize the system prompt due to a large searching space and the complexity of agent responses.
Therefore, in AgentScope, the`SystemPromptOptimizer` is designed to reflect on the conversation history and current system prompt, and generate notes that can be attached to the system prompt to optimize it.
> Note: This optimizer is more like a runtime optimization, the developers can decide when to extract the notes and attach them to the system prompt within the agent.
> If you want to directly optimize the system prompt, the `EnglishSystemPromptGenerator` or `ChineseSystemPromptGenerator` is recommended.
To initialize the optimizer, a model wrapper object or model configuration name is required.
Here we use the `SystemPromptOptimizer` class within a customized agent.
```python
from agentscope.agents import AgentBase
from agentscope.prompt import SystemPromptOptimizer
from agentscope.message import Msg
from typing import Optional, Union, Sequence
class MyAgent(AgentBase):
def __init__(
self,
name: str,
model_config_name: str,
sys_prompt: str,
) -> None:
super().__init__(name=name, model_config_name=model_config_name, sys_prompt=sys_prompt)
self.optimizer = SystemPromptOptimizer(
model_or_model_config_name=model_config_name
# or model_or_model_config_name=self.model
)
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
self.memory.add(x)
prompt = self.model.format(
Msg(self.name, self.sys_prompt, "system"),
self.memory.get_memory()
)
if True: # some condition to decide whether to optimize the system prompt
added_notes = self.optimizer.generate_notes(prompt, self.memory.get_memory())
self.sys_prompt += "\n".join(added_notes)
res = self.model(prompt)
msg = Msg(self.name, res.text, "assistant")
self.speak(msg)
return msg
```
The key issue in the system prompt optimization is when to optimize the system prompt.
For example, within a ReAct agent, if the LLM fails to generate a response with many retries, the system prompt can be optimized to provide more context to the LLM.
[[Back to the top]](#209-prompt-opt)

View File

@@ -0,0 +1,302 @@
(210-rag-en)=
# A Quick Introduction to RAG in AgentScope
We want to introduce three concepts related to RAG in AgentScope: Knowledge, KnowledgeBank and RAG agent.
### Knowledge
The Knowledge modules (now only `LlamaIndexKnowledge`; support for LangChain will come soon) are responsible for handling all RAG-related operations.
#### How to create a Knowledge object
A Knowledge object can be created with a JSON configuration to specify 1) data path, 2) data loader, 3) data preprocessing methods, and 4) embedding model (model config name).
A detailed example can refer to the following:
<details>
<summary> A detailed example of Knowledge object configuration </summary>
```json
[
{
"knowledge_id": "{your_knowledge_id}",
"emb_model_config_name": "{your_embed_model_config_name}",
"data_processing": [
{
"load_data": {
"loader": {
"create_object": true,
"module": "llama_index.core",
"class": "SimpleDirectoryReader",
"init_args": {
"input_dir": "{path_to_your_data_dir_1}",
"required_exts": [".md"]
}
}
}
},
{
"load_data": {
"loader": {
"create_object": true,
"module": "llama_index.core",
"class": "SimpleDirectoryReader",
"init_args": {
"input_dir": "{path_to_your_python_code_data_dir}",
"recursive": true,
"required_exts": [".py"]
}
}
},
"store_and_index": {
"transformations": [
{
"create_object": true,
"module": "llama_index.core.node_parser",
"class": "CodeSplitter",
"init_args": {
"language": "python",
"chunk_lines": 100
}
}
]
}
}
]
}
]
```
</details>
#### More about knowledge configurations
The aforementioned configuration is usually saved as a JSON file, it musts
contain the following key attributes,
* `knowledge_id`: a unique identifier of the knowledge;
* `emb_model_config_name`: the name of the embedding model;
* `chunk_size`: default chunk size for the document transformation (node parser);
* `chunk_overlap`: default chunk overlap for each chunk (node);
* `data_processing`: a list of data processing methods.
##### Using LlamaIndexKnowledge as an example
Regarding the last attribute `data_processing`, each entry of the list (which is a dict) configures a data
loader object that loads the needed data (i.e. `load_data`),
and a transformation object that the process the loaded data (`store_and_index`).
Accordingly, one may load data from multiple sources (with different data loaders),
process with individually defined manners (i.e. transformation or node parser),
and merge the processed data into a single index for later retrieval.
For more information about the components, please refer to
[LlamaIndex-Loading Data](https://docs.llamaindex.ai/en/stable/module_guides/loading/).
In common, we need to set the following attributes
* `create_object`: indicates whether to create a new object, must be true in this case;
* `module`: where the class is located;
* `class`: the name of the class.
More specifically, for setting the `load_data`, you can use a wide collection of data loaders,
such as `SimpleDirectoryReader` (in `class`), provided by Llama-index, to load a various collection of data types
(e.g. txt, pdf, html, py, md, etc.). Regarding this data loader, you can set the following attributes
* `input_dir`: the path to the data directory;
* `required_exts`: the file extensions that the data loader will load.
For more information about the data loaders, please refer to [here](https://docs.llamaindex.ai/en/stable/module_guides/loading/simpledirectoryreader/)
For `store_and_index`, it is optional and if it is not specified, the default transformation (a.k.a. node parser) is `SentenceSplitter`. For some specific node parser such as `CodeSplitter`, users can set the following attributes:
* `language`: the language of the code;
* `chunk_lines`: the number of lines for each of the code chunk.
For more information about the node parsers, please refer to [here](https://docs.llamaindex.ai/en/stable/module_guides/loading/node_parsers/).
If users want to avoid the detailed configuration, we also provide a quick way in `KnowledgeBank` (see the following).
#### How to use a Knowledge object
After a knowledge object is created successfully, users can retrieve information related to their queries by calling `.retrieve(...)` function.
The `.retrieve` function accepts at least three basic parameters:
* `query`: input that will be matched in the knowledge;
* `similarity_top_k`: how many most similar "data blocks" will be returned;
* `to_list_strs`: whether return the retrieved information as strings.
*Advanaced:* In `LlamaIndexKnowledge`, it also supports users passing their own retriever to retrieve from knowledge.
#### More details inside `LlamaIndexKnowledge`
Here, we will use `LlamaIndexKnowledge` as an example to illustrate the operation within the `Knowledge` module.
When a `LlamaIndexKnowledge` object is initialized, the `LlamaIndexKnowledge.__init__` will go through the following steps:
* It processes data and prepare for retrieval in `LlamaIndexKnowledge._data_to_index(...)`, which includes
* loading the data `LlamaIndexKnowledge._data_to_docs(...)`;
* preprocessing the data with preprocessing methods (e.g., splitting) and embedding model `LlamaIndexKnowledge._docs_to_nodes(...)`;
* get ready for being query, i.e. generate indexing for the processed data.
* If the indexing already exists, then `LlamaIndexKnowledge._load_index(...)` will be invoked to load the index and avoid repeating embedding calls.
</br>
### Knowledge Bank
The knowledge bank maintains a collection of Knowledge objects (e.g., on different datasets) as a set of *knowledge*. Thus,
different agents can reuse the Knowledge object without unnecessary "re-initialization".
Considering that configuring the Knowledge object may be too complicated for most users, the knowledge bank also provides an easy function call to create Knowledge objects.
* `KnowledgeBank.add_data_as_knowledge`: create Knowledge object. An easy way only requires to provide `knowledge_id`, `emb_model_name` and `data_dirs_and_types`.
As knowledge bank process files as `LlamaIndexKnowledge` by default, all text file types are supported, such as `.txt`, `.html`, `.md`, `.csv`, `.pdf` and all code file like `.py`. File types other than the text can refer to [LlamaIndex document](https://docs.llamaindex.ai/en/stable/module_guides/loading/simpledirectoryreader/).
```python
knowledge_bank.add_data_as_knowledge(
knowledge_id="agentscope_tutorial_rag",
emb_model_name="qwen_emb_config",
data_dirs_and_types={
"../../docs/sphinx_doc/en/source/tutorial": [".md"],
},
)
```
More advance initialization, users can still pass a knowledge config as a parameter `knowledge_config`:
```python
# load knowledge_config as dict
knowledge_bank.add_data_as_knowledge(
knowledge_id=knowledge_config["knowledge_id"],
emb_model_name=knowledge_config["emb_model_config_name"],
knowledge_config=knowledge_config,
)
```
* `KnowledgeBank.get_knowledge`: It accepts two parameters, `knowledge_id` and `duplicate`.
It will return a knowledge object with the provided `knowledge_id`; if `duplicate` is true, the return will be deep copied.
* `KnowledgeBank.equip`: It accepts three parameters, `agent`, `knowledge_id_list` and `duplicate`.
The function will provide knowledge objects according to the `knowledge_id_list` and put them into `agent.knowledge_list`. If `duplicate` is true, the assigned knowledge object will be deep copied first.
### RAG agent
RAG agent is an agent that can generate answers based on the retrieved knowledge.
* Agent using RAG: a RAG agent has a list of knowledge objects (`knowledge_list`).
* RAG agent can be initialized with a `knowledge_list`
```python
knowledge = knowledge_bank.get_knowledge(knowledge_id)
agent = LlamaIndexAgent(
name="rag_worker",
sys_prompt="{your_prompt}",
model_config_name="{your_model}",
knowledge_list=[knowledge], # provide knowledge object directly
similarity_top_k=3,
log_retrieval=False,
recent_n_mem_for_retrieve=1,
)
```
* If RAG agent is build with a configurations with `knowledge_id_list` specified, agent can load specific knowledge from a `KnowledgeBank` by passing it and a list ids into the `KnowledgeBank.equip` function.
```python
# >>> agent.knowledge_list
# >>> []
knowledge_bank.equip(agent, agent.knowledge_id_list)
# >>> agent.knowledge_list
# [<LlamaIndexKnowledge object at 0x16e516fb0>]
```
* Agent can use the retrieved knowledge in the `reply` function and compose their prompt to LLMs.
**Building RAG agent yourself.** As long as you provide a list of knowledge id, you can pass it with your agent to the `KnowledgeBank.equip`.
Your agent will be equipped with a list of knowledge according to the `knowledge_id_list`.
You can decide how to use the retrieved content and even update and refresh the index in your agent's `reply` function.
## (Optional) Setting up a local embedding model service
For those who are interested in setting up a local embedding service, we provide the following example based on the
`sentence_transformers` package, which is a popular specialized package for embedding models (based on the `transformer` package and compatible with both HuggingFace and ModelScope models).
In this example, we will use one of the SOTA embedding models, `gte-Qwen2-7B-instruct`.
* Step 1: Follow the instruction on [HuggingFace](https://huggingface.co/Alibaba-NLP/gte-Qwen2-7B-instruct) or [ModelScope](https://www.modelscope.cn/models/iic/gte_Qwen2-7B-instruct ) to download the embedding model.
(For those who cannot access HuggingFace directly, you may want to use a HuggingFace mirror by running a bash command
`export HF_ENDPOINT=https://hf-mirror.com` or add a line of code `os.environ["HF_ENDPOINT"] = "https://hf-mirror.com"` in your Python code.)
* Step 2: Set up the server. The following code is for reference.
```python
import datetime
import argparse
from flask import Flask
from flask import request
from sentence_transformers import SentenceTransformer
def create_timestamp(format_: str = "%Y-%m-%d %H:%M:%S") -> str:
"""Get current timestamp."""
return datetime.datetime.now().strftime(format_)
app = Flask(__name__)
@app.route("/embedding/", methods=["POST"])
def get_embedding() -> dict:
"""Receive post request and return response"""
json = request.get_json()
inputs = json.pop("inputs")
global model
if isinstance(inputs, str):
inputs = [inputs]
embeddings = model.encode(inputs)
return {
"data": {
"completion_tokens": 0,
"messages": {},
"prompt_tokens": 0,
"response": {
"data": [
{
"embedding": emb.astype(float).tolist(),
}
for emb in embeddings
],
"created": "",
"id": create_timestamp(),
"model": "flask_model",
"object": "text_completion",
"usage": {
"completion_tokens": 0,
"prompt_tokens": 0,
"total_tokens": 0,
},
},
"total_tokens": 0,
"username": "",
},
}
if __name__ == "__main__":
parser = argparse.ArgumentParser()
parser.add_argument("--model_name_or_path", type=str, required=True)
parser.add_argument("--device", type=str, default="auto")
parser.add_argument("--port", type=int, default=8000)
args = parser.parse_args()
global model
print("setting up for embedding model....")
model = SentenceTransformer(
args.model_name_or_path
)
app.run(port=args.port)
```
* Step 3: start server.
```bash
python setup_ms_service.py --model_name_or_path {$PATH_TO_gte_Qwen2_7B_instruct}
```
Testing whether the model is running successfully.
```python
from agentscope.models.post_model import PostAPIEmbeddingWrapper
model = PostAPIEmbeddingWrapper(
config_name="test_config",
api_url="http://127.0.0.1:8000/embedding/",
json_args={
"max_length": 4096,
"temperature": 0.5
}
)
print(model("testing"))
```
[[Back to the top]](#210-rag-en)

View File

@@ -0,0 +1,30 @@
(301-community-en)=
# Joining AgentScope Community
Becoming a part of the AgentScope community allows you to connect with other users and developers. You can share insights, ask questions, and keep up-to-date with the latest developments and interesting multi-agent applications. Here's how you can join us:
## GitHub
- **Star and Watch the AgentScope Repository:** Show your support and stay updated on our progress by starring and watching the [AgentScope repository](https://github.com/modelscope/agentscope).
- **Submit Issues and Pull Requests:** If you encounter any problems or have suggestions, submit an issue to the relevant repository. We also welcome pull requests for bug fixes, improvements, or new features.
## Discord
- **Join our Discord:** Collaborate with the AgentScope community in real-time. Engage in discussions, seek assistance, and share your experiences and insights on [Discord](https://discord.gg/eYMpfnkG8h).
## DingTalk (钉钉)
- **Connect on DingTalk:** We are also available on DingTalk. Join our group to chat, and stay informed about AgentScope-related news and updates.
Scan the QR code below on DingTalk to join:
<img width="150" src="https://img.alicdn.com/imgextra/i2/O1CN01tuJ5971OmAqNg9cOw_!!6000000001747-0-tps-444-460.jpg" alt="AgentScope-dingtalk">
Our DingTalk group invitation: [AgentScope DingTalk Group](https://qr.dingtalk.com/action/joingroup?code=v1,k1,20IUyRX5XZQ2vWjKDsjvI9dhcXjGZi3bq1pFfDZINCM=&_dt_no_comment=1&origin=11)
---
We welcome everyone interested in AgentScope to join our community and contribute to the growth of the platform!
[[Return to the top]](#301-community-en)

View File

@@ -0,0 +1,70 @@
(302-contribute-en)=
# Contribute to AgentScope
Our community thrives on the diverse ideas and contributions of its members. Whether you're fixing a bug, adding a new feature, improving the documentation, or adding examples, your help is welcome. Here's how you can contribute:
## Report Bugs and Ask For New Features?
Did you find a bug or have a feature request? Please first check the issue tracker to see if it has already been reported. If not, feel free to open a new issue. Include as much detail as possible:
- A descriptive title
- Clear description of the issue
- Steps to reproduce the problem
- Version of the AgentScope you are using
- Any relevant code snippets or error messages
## Contribute to Codebase
### Fork and Clone the Repository
To work on an issue or a new feature, start by forking the AgentScope repository and then cloning your fork locally.
```bash
git clone https://github.com/your-username/agentscope.git
cd agentscope
```
### Create a New Branch
Create a new branch for your work. This helps keep proposed changes organized and separate from the `main` branch.
```bash
git checkout -b your-feature-branch-name
```
### Making Changes
With your new branch checked out, you can now make your changes to the code. Remember to keep your changes as focused as possible. If you're addressing multiple issues or features, it's better to create separate branches and pull requests for each.
We provide a developer version with additional `pre-commit` hooks to perform format checks compared to the official version:
```bash
# Install the developer version
pip install -e .[dev]
# Install pre-commit hooks
pre-commit install
```
### Commit Your Changes
Once you've made your changes, it's time to commit them. Write clear and concise commit messages that explain your changes.
```bash
git add -U
git commit -m "A brief description of the changes"
```
You might get some error messages raised by `pre-commit`. Please resolve them according to the error code and commit again.
### Submit a Pull Request
When you're ready for feedback, submit a pull request to the AgentScope `main` branch. In your pull request description, explain the changes you've made and any other relevant context.
We will review your pull request. This process might involve some discussion, additional changes on your part, or both.
### Code Review
Wait for us to review your pull request. We may suggest some changes or improvements. Keep an eye on your GitHub notifications and be responsive to any feedback.
[[Return to the top]](#302-contribute-en)

View File

@@ -0,0 +1,8 @@
Get Involved
===============
.. toctree::
:maxdepth: 2
301-community.md
302-contribute.md

View File

@@ -0,0 +1,34 @@
# Welcome to AgentScope Tutorial
AgentScope is an innovative multi-agent platform designed to empower developers to build multi-agent applications with ease, reliability, and high performance. It features three high-level capabilities:
- **Easy-to-Use**: Programming in pure Python with various prebuilt components for immediate use, suitable for developers or users with different levels of customization requirements.
- **High Robustness**: Supporting customized fault-tolerance controls and retry mechanisms to enhance application stability.
- **Actor-Based Distribution**: Enabling developers to build distributed multi-agent applications in a centralized programming manner for streamlined development.
## Tutorial Navigator
- [About AgentScope](101-agentscope.md)
- [Installation](102-installation.md)
- [Quick Start](103-example.md)
- [Model](203-model.md)
- [Prompt Engineering](206-prompt.md)
- [Agent](201-agent.md)
- [Memory](205-memory.md)
- [Response Parser](203-parser.md)
- [System Prompt Optimization](209-prompt_opt.md)
- [Tool](204-service.md)
- [Pipeline and MsgHub](202-pipeline.md)
- [Distribution](208-distribute.md)
- [AgentScope Studio](209-gui.md)
- [Retrieval Augmented Generation (RAG)](210-rag.md)
- [Logging](105-logging.md)
- [Monitor](207-monitor.md)
- [Example: Werewolf Game](104-usecase.md)
### Getting Involved
- [Joining AgentScope Community](301-community.md)
- [Contribute to AgentScope](302-contribute.md)