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,2 @@
# -*- coding: utf-8 -*-
# Your complete code here.

View File

@@ -0,0 +1,101 @@
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# Your Example Title Here\n",
"\n",
"This example will show\n",
"- How to ...\n",
"- How to ...\n",
"\n",
"\n",
"## Background (If needed)\n",
"\n",
"The background of your example here. \n",
"\n",
"\n",
"## Tested Models\n",
"\n",
"These models are tested in this example. For other models, some modifications may be needed. \n",
"- xxx\n",
"- xxx \n",
"\n",
"\n",
"## Prerequisites\n",
"\n",
"Fill the next cell to meet the following requirements \n",
"- The requirements to execute this example\n",
"- ...\n",
"- [Optional] Optional requirements\n",
"- ...\n"
],
"metadata": {
"collapsed": false
},
"id": "5f0c2fc19efe575e"
},
{
"cell_type": "code",
"execution_count": null,
"outputs": [],
"source": [
"# The prerequisites, e.g.\n",
"bing_api_key = \"xxx\"\n",
"\n",
"your_model_configuration_name = \"xxx\"\n",
"\n",
"your_model_configuration = {\n",
" \"model_type\": \"xxx\", \n",
" \"config_name\": your_model_configuration_name,\n",
" \n",
" # ...\n",
"}"
],
"metadata": {
"collapsed": false,
"is_executing": true
},
"id": "596cca552b163051"
},
{
"cell_type": "code",
"execution_count": null,
"outputs": [],
"source": [],
"metadata": {
"collapsed": false
},
"id": "966b96e4893709a5"
},
{
"cell_type": "markdown",
"source": [],
"metadata": {
"collapsed": false
},
"id": "7cbb4630bf5e2c15"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,26 @@
# Your Example Title Here
This example will show
- How to ...
- How to ...
## Background (If needed)
The background of your example here.
## Tested Models
These models are tested in this example. For other models, some modifications may be needed.
- xxx
- xxx
## Prerequisites
Fill the next cell to meet the following requirements
- The requirements to execute this example
- ...
- [Optional] Optional requirements
- ...

View File

@@ -0,0 +1,2 @@
# -*- coding: utf-8 -*-
# Your complete code here.

View File

@@ -0,0 +1,21 @@
# Multi-Agent Conversation in AgentScope
This example will show how to program a multi-agent conversation in AgentScope.
Complete code is in `conversation.py`, which set up a user agent and an
assistant agent to have a conversation. When user input "exit", the
conversation ends.
You can modify the `sys_prompt` to change the role of assistant agent.
```bash
# Note: Set your api_key in conversation.py first
python conversation.py
```
## Tested Models
These models are tested in this example. For other models, some modifications may be needed.
- dashscope_chat (qwen-max)
- ollama_chat (ollama_llama3_8b)
- gemini_chat (models/gemini-pro)
## Prerequisites
To set up model serving with open-source LLMs, follow the guidance in
[scripts/REAMDE.md](../../scripts/README.md).

View File

@@ -0,0 +1,49 @@
# -*- coding: utf-8 -*-
"""A simple example for conversation between user and assistant agent."""
import agentscope
from agentscope.agents import DialogAgent
from agentscope.agents.user_agent import UserAgent
from agentscope.pipelines.functional import sequentialpipeline
def main() -> None:
"""A basic conversation demo"""
agentscope.init(
model_configs=[
{
"model_type": "openai_chat",
"config_name": "gpt-3.5-turbo",
"model_name": "gpt-3.5-turbo",
"api_key": "xxx", # Load from env if not provided
"organization": "xxx", # Load from env if not provided
"generate_args": {
"temperature": 0.5,
},
},
{
"model_type": "post_api_chat",
"config_name": "my_post_api",
"api_url": "https://xxx",
"headers": {},
},
],
project="Multi-Agent Conversation",
)
# Init two agents
dialog_agent = DialogAgent(
name="Assistant",
sys_prompt="You're a helpful assistant.",
model_config_name="gpt-3.5-turbo", # replace by your model config name
)
user_agent = UserAgent()
# start the conversation between user and assistant
x = None
while x is None or x.content != "exit":
x = sequentialpipeline([dialog_agent, user_agent], x)
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,22 @@
[
{
"config_name": "gpt-3.5-turbo",
"model_type": "openai-chat",
"model_name": "gpt-3.5-turbo",
"api_key": "xxx",
"organization": "xxx",
"generate_args": {
"temperature": 0.0
}
},
{
"config_name": "gpt-4",
"model_type": "openai-chat",
"model_name": "gpt-4",
"api_key": "xxx",
"organization": "xxx",
"generate_args": {
"temperature": 0.5
}
}
]

View File

@@ -0,0 +1,85 @@
PRAGMA foreign_keys = ON;
CREATE TABLE "stadium" (
"Stadium_ID" int,
"Location" text,
"Name" text,
"Capacity" int,
"Highest" int,
"Lowest" int,
"Average" int,
PRIMARY KEY ("Stadium_ID")
);
INSERT INTO "stadium" VALUES (1,"Raith Rovers","Stark's Park","10104","4812","1294","2106");
INSERT INTO "stadium" VALUES (2,"Ayr United","Somerset Park","11998","2363","1057","1477");
INSERT INTO "stadium" VALUES (3,"East Fife","Bayview Stadium","2000","1980","533","864");
INSERT INTO "stadium" VALUES (4,"Queen's Park","Hampden Park","52500","1763","466","730");
INSERT INTO "stadium" VALUES (5,"Stirling Albion","Forthbank Stadium","3808","1125","404","642");
INSERT INTO "stadium" VALUES (6,"Arbroath","Gayfield Park","4125","921","411","638");
INSERT INTO "stadium" VALUES (7,"Alloa Athletic","Recreation Park","3100","1057","331","637");
INSERT INTO "stadium" VALUES (9,"Peterhead","Balmoor","4000","837","400","615");
INSERT INTO "stadium" VALUES (10,"Brechin City","Glebe Park","3960","780","315","552");
CREATE TABLE "singer" (
"Singer_ID" int,
"Name" text,
"Country" text,
"Song_Name" text,
"Song_release_year" text,
"Age" int,
"Is_male" bool,
PRIMARY KEY ("Singer_ID")
);
INSERT INTO "singer" VALUES (1,"Joe Sharp","Netherlands","You","1992",52,"F");
INSERT INTO "singer" VALUES (2,"Timbaland","United States","Dangerous","2008",32,"T");
INSERT INTO "singer" VALUES (3,"Justin Brown","France","Hey Oh","2013",29,"T");
INSERT INTO "singer" VALUES (4,"Rose White","France","Sun","2003",41,"F");
INSERT INTO "singer" VALUES (5,"John Nizinik","France","Gentleman","2014",43,"T");
INSERT INTO "singer" VALUES (6,"Tribal King","France","Love","2016",25,"T");
CREATE TABLE "concert" (
"concert_ID" int,
"concert_Name" text,
"Theme" text,
"Stadium_ID" text,
"Year" text,
PRIMARY KEY ("concert_ID"),
FOREIGN KEY ("Stadium_ID") REFERENCES "stadium"("Stadium_ID")
);
INSERT INTO "concert" VALUES (1,"Auditions","Free choice",1,2014);
INSERT INTO "concert" VALUES (2,"Super bootcamp","Free choice 2",2,2014);
INSERT INTO "concert" VALUES (3,"Home Visits","Bleeding Love",2,2015);
INSERT INTO "concert" VALUES (4,"Week 1","Wide Awake",10,2014);
INSERT INTO "concert" VALUES (5,"Week 1","Happy Tonight",9,2015);
INSERT INTO "concert" VALUES (6,"Week 2","Party All Night",7,2015);
CREATE TABLE "singer_in_concert" (
"concert_ID" int,
"Singer_ID" text,
PRIMARY KEY ("concert_ID","Singer_ID"),
FOREIGN KEY ("concert_ID") REFERENCES "concert"("concert_ID"),
FOREIGN KEY ("Singer_ID") REFERENCES "singer"("Singer_ID")
);
INSERT INTO "singer_in_concert" VALUES (1, 2);
INSERT INTO "singer_in_concert" VALUES (1, 3);
INSERT INTO "singer_in_concert" VALUES (1, 5);
INSERT INTO "singer_in_concert" VALUES (2, 3);
INSERT INTO "singer_in_concert" VALUES (2, 6);
INSERT INTO "singer_in_concert" VALUES (3, 5);
INSERT INTO "singer_in_concert" VALUES (4, 4);
INSERT INTO "singer_in_concert" VALUES (5, 6);
INSERT INTO "singer_in_concert" VALUES (5, 3);
INSERT INTO "singer_in_concert" VALUES (6, 2);

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,498 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Convert Natural Language to SQL Query with ReAct Agent\n",
"\n",
"This notebook will show \n",
"- how to use ReAct agent to convert natural language to SQL query\n",
"- how to use a customized NL2SQL algorithm, DAIL-SQL, in AgentScope \n",
"\n",
"## Background\n",
"\n",
"NL2SQl is a challenging task in both database and natural language processing communities. It aims to convert natural language question into SQL query. In research community, there are many works exploring the potential of LLMs in NL2SQL, and it would be very interesting to explore this task with LLM-empowered agents. \n",
"\n",
"In this notebook, we try to equip the ReAct agent with a SOTA NL2SQL algorithm, DAIL-SQL, to see if the agent-level reasoning could bring any insights to this task. The implementation of DAIL-SQL refer to its [GitHub repository](https://github.com/BeachWang/DAIL-SQL) and [paper](https://arxiv.org/abs/2308.15363).\n",
"\n",
"## Prerequisites\n",
"\n",
"- Follow [READMD.md](https://github.com/modelscope/agentscope) to install AgentScope.\n",
"- Install the third-party libraries used in [DAIL-SQL](https://github.com/BeachWang/DAIL-SQL/blob/main/requirements.txt).\n",
"- Prepare a model configuration. AgentScope supports both local deployed model services (CPU or GPU) and third-party services. More details and example model configurations please refer to our [tutorial](https://modelscope.github.io/agentscope/en/tutorial/203-model.html).\n",
"- Get familiar with service functions, ServiceToolkit module and build-in ReAct agent. The corresponding example can be found in [ReAct agent example](../conversation_with_react_agent/main.ipynb).\n",
"\n",
"## Note\n",
"\n",
"- The example is tested with the following models. For other models, you may need to adjust the prompt.\n",
" - gpt-4 (The model that [DAIL-SQL](https://github.com/BeachWang/DAIL-SQL) is built on)\n",
" - gpt-3.5-turbo "
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 1: Initializing model config and SQL database\n",
"First, we initalize the agentscope config and load the corresponding model."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from typing import Callable\n",
"import agentscope\n",
"from agentscope.models import load_model_by_config_name\n",
"agentscope.init(\n",
" model_configs=\"./configs/model_configs.json\",\n",
" project=\"Conversation with NL2SQL\",\n",
")\n",
"loaded_model = load_model_by_config_name('gpt-4')"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Then, we init and provide the corresponding database path in sqlite file format.\n",
"Here we generate the sqlite file using the provided sql commands [here](./database/concert_singer/schema.sql). \n",
"You can also use the .sqlite format file directly."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from sql_utils import create_sqlite_db_from_schema\n",
"example_db_id = \"concert_singer\"\n",
"db_schema_path = \"./database/concert_singer/schema.sql\"\n",
"db_sqlite_path = \"./database/concert_singer/concert_singer.sqlite\"\n",
"create_sqlite_db_from_schema(db_schema_path, db_sqlite_path)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"We can show a general description of the database schema using the code below."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"The database consists of four tables: \n",
"\n",
"1. Stadium: This table includes the following fields: Stadium_ID (which is the primary key), Location, Name, Capacity, Highest, Lowest, and Average.\n",
"\n",
"2. Singer: This table includes the following fields: Singer_ID (which is the primary key), Name, Country, Song_Name, Song_release_year, Age, and Is_male (a Boolean field that indicates the singer's gender).\n",
"\n",
"3. Concert: This table is linked to the Stadium table via the Stadium_ID field. The table includes the fields concert_ID (which is the primary key), concert_Name, Theme, Stadium_ID, and Year.\n",
"\n",
"4. Singer_in_Concert: This table acts as a junction table to establish a many-to-many relationship between the Singer and Concert tables. It includes the fields concert_ID and Singer_ID, which both make up the primary key for the table. These are also reference as foreign keys to their respective basetable: the Concert table via the concert_ID field, and the Singer table via the Singer_ID field.\n",
"\n",
"In summary, this database seems designed to track concerts, including details about singers, the concert's location (stadium), and which singer performed at which concert.\n"
]
}
],
"source": [
"from loguru import logger\n",
"from sql_utils import SQLPrompt\n",
"describe_prompt = SQLPrompt().describe_schema(db_sqlite_path)\n",
"sql_description = loaded_model([{\"role\": \"assistant\", \"content\": describe_prompt}]).text\n",
"logger.chat(sql_description)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 2: Setting up tool functions\n",
"Then, we define the tools needed for React Agent to perform SQL query. Namely, our agent should be able to generate sql query given natural language and perform sql query to get the result.\n",
"\n",
"- We referenced a third-party Text-to-SQL [DAIL-SQL](https://github.com/BeachWang/DAIL-SQL) to generate Text-to-SQL prompt.\n",
"- We use the query_sqlite service function in the agentscope.service module. "
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [],
"source": [
"from agentscope.service import (\n",
" ServiceResponse, \n",
" ServiceExecStatus, \n",
" ServiceToolkit, \n",
" query_sqlite,\n",
")\n",
"from sql_utils import DailSQLPromptGenerator\n",
"\n",
"\n",
"def generate_sql_query(question: str, db_path: str, model: Callable) -> ServiceResponse:\n",
" \"\"\"\n",
" Generate the sql query based on the natural language question from user.\n",
" \n",
" Args:\n",
" question (`str`):\n",
" The question asked by user in natural language.\n",
" db_path (`str`):\n",
" The path to the .sqlite file.\n",
" model (`Callable`):\n",
" The LLM model used to generate the SQL query.\n",
" \"\"\"\n",
" prompt_helper = DailSQLPromptGenerator(db_path)\n",
" prepared_prompt = prompt_helper.generate_prompt({\"content\": question})\n",
" \n",
" def get_response_from_prompt(prompt: dict, model: Callable) -> str:\n",
" \"\"\"\n",
" Generate response from prompt using LLM\n",
" \"\"\"\n",
" messages = [{\"role\": \"assistant\", \"content\": prompt}]\n",
" sql = model(messages).text\n",
" sql = \" \".join(sql.replace(\"\\n\", \" \").split())\n",
" sql = sql.strip().split(\"/*\")[0]\n",
" if sql.startswith(\"SELECT\"):\n",
" response = sql + \"\\n\"\n",
" elif sql.startswith(\" \"):\n",
" response = \"SELECT\" + sql + \"\\n\"\n",
" else:\n",
" response = \"SELECT \" + sql + \"\\n\"\n",
" return response\n",
"\n",
" sql_response = get_response_from_prompt(\n",
" prepared_prompt[\"prompt\"], model=model\n",
" )\n",
"\n",
" return ServiceResponse(\n",
" ServiceExecStatus.SUCCESS,\n",
" sql_response\n",
" )\n",
"\n",
"\n",
"# Use Service Toolkit to set up tool functions for LLMs\n",
"service_toolkit = ServiceToolkit()\n",
"service_toolkit.add(generate_sql_query, db_path=db_sqlite_path, model=loaded_model)\n",
"service_toolkit.add(query_sqlite, database=db_sqlite_path)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 3: Create the ReAct agent to help perform SQL queries\n",
"\n",
"Now, we can init the ReAct Agent using the defined tools and interact with the agent."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from agentscope.agents import ReActAgent\n",
"agent = ReActAgent(\n",
" name=\"assistant\",\n",
" model_config_name='gpt-4',\n",
" service_toolkit=service_toolkit,\n",
" sys_prompt=\"You are a helpful agent that preform SQL queries base on natual language instructions.\",\n",
" verbose=True, # set verbose to True to show the reasoning process\n",
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 4: Interact with the SQL ReAct Agent using natural lanugage\n",
"\n",
"Now let's see how the agent have response to our questions:"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"user: How many singers do we have?\n",
"##################### ITER 1, STEP 1: REASONING ######################\n",
"assistant: {\n",
" \"thought\": \"To answer the user's question, I need to find out how many unique singers are listed in the database. I will use the generate_sql_query function to create a SQL query to count distinct singers.\",\n",
" \"speak\": \"Let me find this information for you.\",\n",
" \"function\": [\n",
" {\n",
" \"name\": \"generate_sql_query\",\n",
" \"arguments\": {\n",
" \"question\": \"How many unique singers are we have in the database?\"\n",
" }\n",
" }\n",
" ]\n",
"}\n",
"####################### ITER 1, STEP 2: ACTION #######################\n",
">>> Executing function generate_sql_query ...\n",
">>> END \n",
"system: Execution Results:\n",
"1. generate_sql_query:\n",
" [EXECUTE STATUS]: SUCCESS\n",
" [EXECUTE RESULT]: SELECT To find out how many unique singers are in the database, you would need to count the distinct singer IDs in the \"singer\" table. The appropriate SQL query for this would be: ```sql SELECT COUNT(DISTINCT Singer_ID) FROM singer; ```\n",
"\n",
"\n",
"##################### ITER 2, STEP 1: REASONING ######################\n",
"assistant: {\n",
" \"thought\": \"The generate_sql_query function has provided the necessary SQL query. Now I will use the query_sqlite function to execute this query and retrieve the count of unique singers.\",\n",
" \"speak\": \"I'm now running the query to count the number of unique singers in the database.\",\n",
" \"function\": [\n",
" {\n",
" \"name\": \"query_sqlite\",\n",
" \"arguments\": {\n",
" \"allow_change_data\": false,\n",
" \"query\": \"SELECT COUNT(DISTINCT Singer_ID) FROM singer;\"\n",
" }\n",
" }\n",
" ]\n",
"}\n",
"####################### ITER 2, STEP 2: ACTION #######################\n",
">>> Executing function query_sqlite ...\n",
">>> END \n",
"system: Execution Results:\n",
"1. query_sqlite:\n",
" [EXECUTE STATUS]: SUCCESS\n",
" [EXECUTE RESULT]: [(6,)]\n",
"\n",
"##################### ITER 3, STEP 1: REASONING ######################\n",
"assistant: {\n",
" \"thought\": \"I successfully executed the query to count the distinct singers in the database and received the result.\",\n",
" \"speak\": \"We have 6 unique singers in the database.\",\n",
" \"function\": []\n",
"}\n"
]
}
],
"source": [
"from agentscope.message import Msg\n",
"\n",
"mss = Msg(\n",
" name=\"user\", \n",
" content=\"How many singers do we have?\", \n",
" role=\"user\"\n",
")\n",
"logger.chat(mss)\n",
"\n",
"sql_query_mss1 = agent(mss)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"user: Show all countries and the number of singers in each country.\n",
"##################### ITER 1, STEP 1: REASONING ######################\n",
"assistant: {\n",
" \"thought\": \"To show all countries and the number of singers in each, I'll need to create and run an SQL query that counts singers by their country. This involves grouping the data by country and then counting the number of singers in each group.\",\n",
" \"speak\": \"Let me find out how many singers there are in each country for you.\",\n",
" \"function\": [\n",
" {\n",
" \"name\": \"generate_sql_query\",\n",
" \"arguments\": {\n",
" \"question\": \"Show all countries and the number of singers in each country.\"\n",
" }\n",
" }\n",
" ]\n",
"}\n",
"####################### ITER 1, STEP 2: ACTION #######################\n",
">>> Executing function generate_sql_query ...\n",
">>> END \n",
"system: Execution Results:\n",
"1. generate_sql_query:\n",
" [EXECUTE STATUS]: SUCCESS\n",
" [EXECUTE RESULT]: SELECT To show all countries and the number of singers in each country based on the database schema provided, you can use the SQL query below. This query groups singers by their country and counts the number of singers in each country: ```sql SELECT Country, COUNT(*) AS NumberOfSingers FROM singer GROUP BY Country ``` This SQL query selects the `Country` column from the `singer` table and uses the `COUNT(*)` function to count the total number of singers for each country. The `GROUP BY` clause groups the results by the `Country`, so you get the count of singers for each distinct country listed in the `singer` table.\n",
"\n",
"\n",
"##################### ITER 2, STEP 1: REASONING ######################\n",
"assistant: {\n",
" \"thought\": \"The generate_sql_query function has provided the correct SQL query for showing all countries and the number of singers in each. Now, I need to execute this query using the query_sqlite function to get the desired data.\",\n",
" \"speak\": \"I'm now running the query to find out the number of singers in each country.\",\n",
" \"function\": [\n",
" {\n",
" \"name\": \"query_sqlite\",\n",
" \"arguments\": {\n",
" \"allow_change_data\": false,\n",
" \"query\": \"SELECT Country, COUNT(*) AS NumberOfSingers FROM singer GROUP BY Country\"\n",
" }\n",
" }\n",
" ]\n",
"}\n",
"####################### ITER 2, STEP 2: ACTION #######################\n",
">>> Executing function query_sqlite ...\n",
">>> END \n",
"system: Execution Results:\n",
"1. query_sqlite:\n",
" [EXECUTE STATUS]: SUCCESS\n",
" [EXECUTE RESULT]: [('France', 4), ('Netherlands', 1), ('United States', 1)]\n",
"\n",
"##################### ITER 3, STEP 1: REASONING ######################\n",
"assistant: {\n",
" \"thought\": \"The query has been executed successfully and I got the result indicating the number of singers in each country. Now I will present this data to the user.\",\n",
" \"speak\": \"We have 4 singers from France, 1 singer from Netherlands, and 1 singer from the United States.\",\n",
" \"function\": []\n",
"}\n"
]
}
],
"source": [
"mss = Msg(\n",
" name=\"user\", \n",
" content=\"Show all countries and the number of singers in each country.\", \n",
" role=\"user\"\n",
")\n",
"logger.chat(mss)\n",
"\n",
"sql_query_mss2 = agent(mss)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"user: How many singers have held concert in multiple stadiums? Give me the name of these singers.\n",
"##################### ITER 1, STEP 1: REASONING ######################\n",
"assistant: {\n",
" \"thought\": \"To find out how many singers have held concerts in multiple stadiums and get their names, I need to create a SQL query that joins relevant tables to count the number of distinct stadiums for each singer. Singers with counts greater than one have held concerts in multiple stadiums. I will start by generating the SQL query.\",\n",
" \"speak\": \"Let me find out which singers have held concerts in multiple stadiums for you.\",\n",
" \"function\": [\n",
" {\n",
" \"name\": \"generate_sql_query\",\n",
" \"arguments\": {\n",
" \"question\": \"How many singers have held concerts in multiple stadiums and what are their names?\"\n",
" }\n",
" }\n",
" ]\n",
"}\n",
"####################### ITER 1, STEP 2: ACTION #######################\n",
">>> Executing function generate_sql_query ...\n",
">>> END \n",
"system: Execution Results:\n",
"1. generate_sql_query:\n",
" [EXECUTE STATUS]: SUCCESS\n",
" [EXECUTE RESULT]: SELECT s.Name, COUNT(DISTINCT c.Stadium_ID) FROM singer AS s JOIN singer_in_concert AS sic ON s.Singer_ID = sic.Singer_ID JOIN concert AS c ON sic.concert_ID = c.concert_ID GROUP BY s.Name HAVING COUNT(DISTINCT c.Stadium_ID) > 1\n",
"\n",
"\n",
"##################### ITER 2, STEP 1: REASONING ######################\n",
"assistant: {\n",
" \"thought\": \"The generate_sql_query function successfully generated the necessary SQL query to find singers who have held concerts in multiple stadiums along with their names. I will now use the query_sqlite function to execute this SQL query and retrieve the required information.\",\n",
" \"speak\": \"I'm now running the query to find out which singers have performed in multiple stadiums.\",\n",
" \"function\": [\n",
" {\n",
" \"name\": \"query_sqlite\",\n",
" \"arguments\": {\n",
" \"allow_change_data\": false,\n",
" \"query\": \"SELECT s.Name, COUNT(DISTINCT c.Stadium_ID) FROM singer AS s JOIN singer_in_concert AS sic ON s.Singer_ID = sic.Singer_ID JOIN concert AS c ON sic.concert_ID = c.concert_ID GROUP BY s.Name HAVING COUNT(DISTINCT c.Stadium_ID) > 1\"\n",
" }\n",
" }\n",
" ]\n",
"}\n",
"####################### ITER 2, STEP 2: ACTION #######################\n",
">>> Executing function query_sqlite ...\n",
">>> END \n",
"system: Execution Results:\n",
"1. query_sqlite:\n",
" [EXECUTE STATUS]: SUCCESS\n",
" [EXECUTE RESULT]: [('John Nizinik', 2), ('Justin Brown', 3), ('Timbaland', 2), ('Tribal King', 2)]\n",
"\n",
"##################### ITER 3, STEP 1: REASONING ######################\n",
"assistant: {\n",
" \"thought\": \"The SQL query has been executed successfully and the resulting dataset includes all singers who have held concerts in multiple stadiums.\",\n",
" \"speak\": \"The singers who have held concerts in multiple stadiums are John Nizinik, Justin Brown, Timbaland, and Tribal King.\",\n",
" \"function\": []\n",
"}\n"
]
}
],
"source": [
"mss = Msg(\n",
" name=\"user\", \n",
" content=\"How many singers have held concert in multiple stadiums? Give me the name of these singers.\", \n",
" role=\"user\"\n",
")\n",
"logger.chat(mss)\n",
"sql_query_mss3 = agent(mss)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"You can use the following code to build a conversation with the ReAct agent:\n",
"\n",
"``` python\n",
"from agentscope.agents import UserAgent\n",
"\n",
"user = UserAgent(name=\"User\")\n",
"\n",
"x = None\n",
"while True:\n",
" x = user(x)\n",
" if x.content == \"exit\":\n",
" break\n",
" x = agent(x)\n",
"```"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"The capabilities of the ReAct agent are indeed very powerful. By modifying prompts and expanding the corresponding usage tools, the ReAct agent can leverage the combination of LLM and tools to accomplish more complex tasks.\n",
"\n",
"We encourage users to delve deeper into exploring the Agent's functionalities and to try out the capabilities of the LLM agent on their own."
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

View File

@@ -0,0 +1,304 @@
# -*- coding: utf-8 -*-
"""
Utils and helpers for performing sql querys.
Referenced from https://github.com/BeachWang/DAIL-SQL.
"""
import sqlite3
import json
from typing import Union
from sqlite3 import Connection
import os
import numpy as np
def query_sqlite(
queries: Union[list[str], str],
path_db: str = None,
cur: str = None,
) -> Union[list, str]:
"""Execute queries and return results."""
assert not (
path_db is None and cur is None
), "path_db and cur cannot be NoneType at the same time"
con: Connection
close_in_func = False
if cur is None:
con = sqlite3.connect(path_db)
cur = con.cursor()
close_in_func = True
if isinstance(queries, str):
results = cur.execute(queries).fetchall()
elif isinstance(queries, list):
results = []
for query in queries:
res = cur.execute(query).fetchall()
results.append(res)
else:
raise TypeError(f"queries cannot be {type(queries)}")
# close the connection if needed
if close_in_func:
con.close()
return results
def create_sqlite_db_from_schema(schema_path: str, db_path: str) -> None:
"""Create a SQLite database file from a schema SQL file.
Args:
schema_path: The file path to the schema SQL file.
db_path: The file path for the SQLite database to be created.
"""
if os.path.exists(db_path):
print(
f"Database file '{db_path}' already exists. ",
)
return
conn = sqlite3.connect(db_path)
with open(schema_path, "r", encoding="utf-8") as schema_file:
schema_sql = schema_file.read()
cursor = conn.cursor()
cursor.executescript(schema_sql)
conn.commit()
conn.close()
def get_table_names(path_db: str = None, cur: str = None) -> list:
"""Get names of all tables within the database,
and reuse cur if it's not None"""
table_names = query_sqlite(
queries="SELECT name FROM sqlite_master WHERE type='table'",
path_db=path_db,
cur=cur,
)
table_names = [_[0] for _ in table_names]
return table_names
def get_sql_for_database(path_db: str = None, cur: str = None) -> list:
"""
Get sql table from database
"""
close_in_func = False
if cur is None:
con = sqlite3.connect(path_db)
cur = con.cursor()
close_in_func = True
table_names = get_table_names(path_db, cur)
queries = [
f"SELECT sql FROM sqlite_master WHERE tbl_name='{name}'"
for name in table_names
]
sqls = query_sqlite(queries, path_db, cur)
if close_in_func:
cur.close()
return [_[0][0] for _ in sqls]
class SQLPrompt:
"""SQL prompt helper"""
def __init__(self) -> None:
self.template_info = "/* Given the following database schema: */\n{}"
self.template_question = "/* Answer the following: {} */"
self.template_agent_prompt = (
"You are a helpful agent that preform"
"SQL querys base on natual language instructions."
"Please describe the database schema provided"
"in a simple and understandable manner. "
)
self.is_sql_prompt = (
"Please read the user's question below and "
"determine whether the question is an appropriate "
"query for the given SQL schema. \n"
"If the question is indeed a query pertaining to the SQL schema, "
'respond with "YES". '
"If the question is not a query related to the SQL schema, "
"provide a brief explanation to the user explaining why their "
"question does not correspond to a SQL query within the "
"context of the schema. "
"The user's question is: "
)
def format_target(self, example: dict) -> str:
"""Format sql prompt"""
return self.format_question(example) + "\nSELECT "
def format_question(self, example: dict) -> str:
"""Format question"""
sqls = get_sql_for_database(example["path_db"])
prompt_info = self.template_info.format("\n\n".join(sqls))
prompt_question = self.template_question.format(example["question"])
prompt_components = [prompt_info, prompt_question]
prompt = "\n\n".join(prompt_components)
return prompt
def describe_schema(self, db_path: str) -> str:
"""Describe SQL schema"""
sqls = get_sql_for_database(db_path)
prompt_info = self.template_info.format("\n\n".join(sqls))
prompt_components = [
self.template_agent_prompt,
"DB schema info: ",
prompt_info,
]
prompt = "\n\n".join(prompt_components)
return prompt
def is_sql_question(self, example: dict) -> str:
"""whether the input is a sql question or not"""
sqls = get_sql_for_database(example["path_db"])
prompt_info = self.template_info.format("\n\n".join(sqls))
prompt_components = [
prompt_info,
self.is_sql_prompt,
example["question"],
]
prompt = "\n".join(prompt_components)
return prompt
class QuestionSqlExampleStyle:
"""Provide QA pair as examples"""
def get_example_prefix(self) -> str:
"""get example prefix"""
return (
"/* Some SQL examples are provided based on similar problems: */\n"
)
def format_example(self, example: dict) -> str:
"""format example"""
template_qa = "/* Answer the following: {} */\n{}"
return template_qa.format(example["question"], example["query"])
class EuclideanDistanceExampleSelector:
"""Select similar sample question"""
def __init__(self) -> None:
self.train_json_path = "./database/train.json"
with open(self.train_json_path, "r", encoding="utf-8") as file:
self.train_json = json.load(file)
self.train_questions = [_["question"] for _ in self.train_json]
self.SELECT_MODEL = "sentence-transformers/all-mpnet-base-v2"
from sentence_transformers import SentenceTransformer
self.bert_model = SentenceTransformer(self.SELECT_MODEL, device="cpu")
try:
self.train_embeddings = np.load("./.cache/train_embeddings.npy")
except FileNotFoundError:
self.train_embeddings = self.bert_model.encode(
self.train_questions,
)
np.save("./.cache/train_embeddings.npy", self.train_embeddings)
def get_examples(
self,
target: dict,
num_example: int,
) -> list:
"""Get similar question examples for few shot"""
target_embedding = self.bert_model.encode([target["question"]])
# find the most similar question in train dataset
from sklearn.metrics.pairwise import euclidean_distances
distances = np.squeeze(
euclidean_distances(target_embedding, self.train_embeddings),
).tolist()
pairs = list(zip(distances, range(len(distances))))
train_json = self.train_json
pairs_sorted = sorted(pairs, key=lambda x: x[0])
top_pairs = []
for d, index in pairs_sorted:
top_pairs.append((index, d))
if len(top_pairs) >= num_example:
break
return [train_json[index] for (index, d) in top_pairs]
class DailSQLPromptGenerator:
"""Generate prompt given the dataset and question"""
def __init__(
self,
db_path: str,
) -> None:
self.db_path = db_path
self.sql_prompt = SQLPrompt()
self.question_selector = EuclideanDistanceExampleSelector()
self.question_style = QuestionSqlExampleStyle()
self.SEP_EXAMPLE = "\n\n"
self.scope_factor = 100
self.NUM_EXAMPLE = 9
self.cross_domain = False
def describe_schema(self) -> str:
"""Describe the sql"""
return self.sql_prompt.describe_schema(self.db_path)
def is_sql_question_prompt(self, question: str) -> str:
"""
prompt for LLM to judge whether the question is appropriate
"""
target = {
"path_db": self.db_path,
"question": question,
}
return self.sql_prompt.is_sql_question(target)
def generate_prompt(self, x: dict = None) -> dict:
"""
Generate prompt given input question
"""
question = x["content"]
target = {
"path_db": self.db_path,
"question": question,
}
prompt_target = self.sql_prompt.format_target(target)
if self.NUM_EXAMPLE != 0:
examples = self.question_selector.get_examples(
target,
self.NUM_EXAMPLE * self.scope_factor,
)
prompt_example = []
question = target["question"]
example_prefix = self.question_style.get_example_prefix()
for example in examples:
example_format = self.question_style.format_example(example)
prompt_example.append(example_format)
if len(prompt_example) >= self.NUM_EXAMPLE:
break
n_valid_example = len(prompt_example)
if len(prompt_example) > 0:
prompt = example_prefix + self.SEP_EXAMPLE.join(
prompt_example + [prompt_target],
)
else:
prompt = self.SEP_EXAMPLE.join(
prompt_example + [prompt_target],
)
else:
n_valid_example = 0
prompt = prompt_target
return {
"prompt": prompt,
"n_examples": n_valid_example,
}

View File

@@ -0,0 +1,29 @@
# Self-Organizing Conversation Example
This example will show
- How to set up a self-organizing conversation using the `DialogAgent` and `agent_builder`
- How to extract the discussion scenario and participant agents from the `agent_builder`'s response
- How to conduct a multi-round discussion among the participant agents
## Background
In this example, we demonstrate how to create a self-organizing conversation where the `agent_builder` automatically sets up the agents participating in the discussion based on a given question. The `agent_builder` provides the discussion scenario and the characteristics of the participant agents. The participant agents then engage in a multi-round discussion to solve the given question.
## Tested Models
These models are tested in this example. For other models, some modifications may be needed.
- `dashscope_chat` with `qwen-turbo`
- `ollama_chat` with `llama3_8b`
- `gemini_chat` with `models/gemini-1.0-pro-latest`
## Prerequisites
Fill the next cell to meet the following requirements
- Set up the `model_configs` with the appropriate API keys and endpoints
- Provide the path to the `agent_builder_instruct.txt` file in the `load_txt` function
- Set the desired `max_round` for the discussion
- Provide the `query` or question for the discussion
- [Optional] Adjust the `generate_args` such as `temperature` for the `openai_chat` model

View File

@@ -0,0 +1,19 @@
Act as a group discussion organizer. Please provide the suitable scenario for discussing this question, and list the roles of the people who need to participate in the discussion in order to answer this question, along with their system prompt to describe their characteristics.
The response must in the format of:
#scenario#: <discussion scenario>
#participants#:
* <participant1 type>: <characteristic description>
* <participant2 type>: <characteristic description>
Here are some examples.
Question: Joy can read 8 pages of a book in 20 minutes. How many hours will it take her to read 120 pages?
Answer:
#scenario#: grade school class discussion
#participants#:
* Instructor: Act as an instructor who is in a class group discussion to guide the student group discussion. Please encourage critical thinking. Encourage participants to think critically and challenge assumptions by asking thought-provoking questions or presenting different perspectives.
* broad-minded-student: Act as a student who is broad-minded and is open to trying new or different ways to solve problems. You are in a group discussion with other student under the guidance of the instructor.
* knowledgeable-student: Act as a knowledgeable student and discuss with others to retrieve more information about the topic. If you do not know the answer to a question, please do not share false information
Please give the discussion scenario and the corresponding participants for the following question:
Question: {question}
Answer:

View File

@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
"""A simple example for auto discussion: the agent builder automatically\
set up the agents participating the discussion ."""
from tools import load_txt, extract_scenario_and_participants
import agentscope
from agentscope.agents import DialogAgent
from agentscope.pipelines.functional import sequentialpipeline
from agentscope.message import Msg
model_configs = [
{
"model_type": "openai_chat",
"config_name": "gpt-3.5-turbo",
"model_name": "gpt-3.5-turbo",
"api_key": "xxx", # Load from env if not provided
"organization": "xxx", # Load from env if not provided
"generate_args": {
"temperature": 0.5,
},
},
{
"model_type": "post_api_chat",
"config_name": "my_post_api",
"api_url": "https://xxx",
"headers": {},
"json_args": {},
},
]
agentscope.init(
model_configs=model_configs,
project="Self-Organizing Conversation",
)
# init the self-organizing conversation
agent_builder = DialogAgent(
name="agent_builder",
sys_prompt="You're a helpful assistant.",
model_config_name="my_post_api",
)
max_round = 2
query = "Say the pupil of your eye has a diameter of 5 mm and you have a \
telescope with an aperture of 50 cm. How much more light can the \
telescope gather than your eye?"
# get the discussion scenario and participant agents
x = load_txt(
"examples/conversation_self_organizing/agent_builder_instruct.txt",
).format(
question=query,
)
x = Msg("user", x, role="user")
settings = agent_builder(x)
scenario_participants = extract_scenario_and_participants(settings["content"])
# set the agents that participant the discussion
agents = [
DialogAgent(
name=key,
sys_prompt=val,
model_config_name="my_post_api",
)
for key, val in scenario_participants["Participants"].items()
]
# begin discussion
msg = Msg("user", f"let's discuss to solve the question: {query}", role="user")
for i in range(max_round):
msg = sequentialpipeline(agents, msg)

View File

@@ -0,0 +1,81 @@
# -*- coding: utf-8 -*-
"""some utils of angent_builder example"""
import re
def load_txt(instruction_file: str) -> str:
"""
load .txt file
Arguments:
instruction_file: str type, which is the .txt file pth
Returns:
instruction: str type, which is the str in the instruction_file
"""
with open(instruction_file, "r", encoding="utf-8") as f:
instruction = f.read()
return instruction
# extract scenario和participants
def extract_scenario_and_participants(content: str) -> dict:
"""
extract the scenario and participants from agent builder's response
Arguments:
content: the agent builders response
Returns:
result: dict
Examples:
content: #scenario#: Astronomy club meeting
#participants#:
* Club Leader: Act as the club leader who is knowledgeable about\
astronomy and optics. You are leading a discussion about the \
capabilities of telescopes versus the human eye. Please provide \
accurate information and guide the discussion.
* Curious Member: Act as a curious club member who is interested \
in astronomy but may not know all the technical details. You are \
eager to learn and ask questions.
* Experienced Astronomer: Act as an experienced astronomer who has \
practical experience using telescopes for stargazing. You can \
provide real-world examples and insights into the topic.
Return:
{'Scenario': 'Astronomy club meeting',
'Participants':
{'Club_Leader': 'Act as the club leader who is knowledgeable \
about astronomy and optics. You are leading a discussion about the \
capabilities of telescopes versus the human eye. Please provide\
accurate information and guide the discussion.',
'Curious_Member': 'Act as a curious club member who is interested \
in astronomy but may not know all the technical details. You are \
eager to learn and ask questions.',
'Experienced_Astronomer': 'Act as an experienced astronomer who has\
practical experience using telescopes for stargazing. You can\
provide real-world examples and insights into the topic.'}}
"""
result = {}
# define regular expression
scenario_pattern = r"#scenario#:\s*(.*)"
participants_pattern = r"\*\s*([^:\n]+):\s*([^\n]+)"
# search and extract scenario
scenario_match = re.search(scenario_pattern, content)
if scenario_match:
result["Scenario"] = scenario_match.group(1).strip()
# search and extract participants
participants_matches = re.finditer(participants_pattern, content)
participants_dict = {}
for match in participants_matches:
participant_type, characteristic = match.groups()
participants_dict[
participant_type.strip().replace(" ", "_")
] = characteristic.strip()
result["Participants"] = participants_dict
return result

View File

@@ -0,0 +1,48 @@
# AgentScope Copilot: a Multi-Agent RAG Application
* **What is this example about?**
With the provided implementation and configuration,
you will obtain three different agents who can help you answer different questions about AgentScope.
* **What is this example for?** By this example, we want to show how the agent with retrieval augmented generation (RAG)
capability can be used to build easily.
## Prerequisites
* **Cloning repo:** This example requires cloning the whole AgentScope repo to local.
* **Packages:** This example is built on the LlamaIndex package. Thus, some packages need to be installed before running the example.
```bash
pip install llama-index==0.10.30 llama-index-readers-docstring-walker==0.1.3 tree-sitter==0.21.3 tree-sitter-languages==1.10.2
```
* **Model APIs:** This example uses Dashscope APIs. Thus, we also need an API key for DashScope.
```bash
export DASHSCOPE_API_KEY='YOUR_API_KEY'
```
**Note:** This example has been tested with `dashscope_chat` and `dashscope_text_embedding` model wrapper, with `qwen-max` and `text-embedding-v2` models.
However, you are welcome to replace the Dashscope language and embedding model wrappers or models with other models you like to test.
## Start AgentScope Copilot
* **Terminal:** The most simple way to execute the AgentScope Copilot is running in terminal.
```bash
python ./rag_example.py
```
* **AS gradio:** If you want to have more organized, clean UI, you can also run with our `as_gradio`.
```bash
as_gradio ./rag_example.py
```
### Agents in the example
After you run the example, you may notice that this example consists of three RAG agents:
* `Tutorial-Assistant`: responsible for answering questions based on AgentScope tutorials (markdown files).
* `Code-Search-Assistant`: responsible for answering questions based on AgentScope code base (python files).
* `API-Assistant`: responsible for answering questions based on AgentScope API documents (html files, generated by `sphinx`)
* `Searching-Assistant`: responsible for general search in tutorial and code base (markdown files and code files)
* `Agent-Guiding-Assistant`: responsible for referring the correct agent(s) among the above ones.
Besides the last `Agent-Guiding-Assistant`, all other agents can be configured to answering questions based on other GitHub repo by replacing the `knowledge`.
For more details about how to use the RAG module in AgentScope, please refer to the tutorial.

View File

@@ -0,0 +1,64 @@
[
{
"class": "LlamaIndexAgent",
"args": {
"name": "Tutorial-Assistant",
"description": "Tutorial-Assistant is an agent that can provide answer based on English tutorial material, mainly the markdown files. It can answer general questions about AgentScope.",
"sys_prompt": "You're an assistant helping new users to use AgentScope. The language style is helpful and cheerful. You generate answers based on the provided context. The answer is expected to be no longer than 100 words. If the key words of the question can be found in the provided context, the answer should contain the section name which contains the answer. For example, 'You may refer to SECTION_NAME for more details.'",
"model_config_name": "qwen_config",
"knowledge_id_list": ["agentscope_tutorial_rag"],
"similarity_top_k": 5,
"log_retrieval": false,
"recent_n_mem_for_retrieve": 1
}
},
{
"class": "LlamaIndexAgent",
"args": {
"name": "Code-Search-Assistant",
"description": "Code-Search-Assistant is an agent that can provide answer based on AgentScope code base. It can answer questions about specific modules in AgentScope.",
"sys_prompt": "You're a coding assistant of AgentScope. The answer starts with appreciation for the question, then provide details regarding the functionality and features of the modules mentioned in the question. The language should be in a professional and simple style. The answer is limited to be less than 100 words.",
"model_config_name": "qwen_config",
"knowledge_id_list": ["agentscope_code_rag"],
"similarity_top_k": 5,
"log_retrieval": false,
"recent_n_mem_for_retrieve": 1
}
},
{
"class": "LlamaIndexAgent",
"args": {
"name": "API-Assistant",
"description": "API-Assistant is an agent that can answer questions about APIs in AgentScope. It can answer general questions about AgentScope.",
"sys_prompt": "You're an assistant providing answers to the questions related to APIs (functions and classes) in AgentScope. The language style is helpful and cheerful. You generate answers based on the provided context. The answer is expected to be no longer than 200 words. If the key words of the question can be found in the provided context, the answer should contain the module of the API. For example, 'You may refer to MODULE_NAME for more details.'",
"model_config_name": "qwen_config",
"knowledge_id_list": ["agentscope_api_rag"],
"similarity_top_k": 2,
"log_retrieval": true,
"recent_n_mem_for_retrieve": 1
}
},
{
"class": "LlamaIndexAgent",
"args": {
"name": "Searching-Assistant",
"description": "Search-Assistant is an agent that can provide answer based on AgentScope code and tutorial. It can answer questions about everything in AgentScope codes and tutorials.",
"sys_prompt": "You're a helpful assistant of AgentScope. The answer starts with appreciation for the question, then provide output the location of the code or section that the most relevant to the question. The answer is limited to be less than 50 words.",
"model_config_name": "qwen_config",
"knowledge_id_list": ["agentscope_code_rag","agentscope_tutorial_rag"],
"similarity_top_k": 5,
"log_retrieval": false,
"recent_n_mem_for_retrieve": 1,
"persist_dir": "./rag_storage/searching_assist"
}
},
{
"class": "DialogAgent",
"args": {
"name": "Agent-Guiding-Assistant",
"sys_prompt": "You're an assistant guiding the user to specific agent for help. The answer is in a cheerful styled language. The output starts with appreciation for the question. Next, rephrase the question in a simple declarative Sentence for example, 'I think you are asking...'. Last, if the question is about detailed code or example in AgentScope Framework, output '@ Code-Search-Assistant you might be suitable for answering the question'; if the question is about API or function calls (Example: 'Is there function related...' or 'how can I initialize ...' ) in AgentScope, output '@ API-Assistant, I think you are more suitable for the question, please tell us more about it'; if question is about where to find some context (Example:'where can I find...'), output '@ Searching-Assistant, we need your help', otherwise, output '@ Tutorial-Assistant, I think you are more suitable for the question, can you tell us more about it?'. The answer is expected to be only one sentence",
"model_config_name": "qwen_config",
"use_memory": false
}
}
]

View File

@@ -0,0 +1,114 @@
[
{
"knowledge_id": "agentscope_code_rag",
"emb_model_config_name": "qwen_emb_config",
"chunk_size": 2048,
"chunk_overlap": 40,
"data_processing": [
{
"load_data": {
"loader": {
"create_object": true,
"module": "llama_index.core",
"class": "SimpleDirectoryReader",
"init_args": {
"input_dir": "../../src/agentscope",
"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
}
}
]
}
}
]
},
{
"knowledge_id": "agentscope_api_rag",
"emb_model_config_name": "qwen_emb_config",
"chunk_size": 1024,
"chunk_overlap": 40,
"data_processing": [
{
"load_data": {
"loader": {
"create_object": true,
"module": "llama_index.core",
"class": "SimpleDirectoryReader",
"init_args": {
"input_dir": "../../docs/docstring_html/",
"required_exts": [
".html"
]
}
}
}
}
]
},
{
"knowledge_id": "agentscope_global_rag",
"emb_model_config_name": "qwen_emb_config",
"chunk_size": 2048,
"chunk_overlap": 40,
"data_processing": [
{
"load_data": {
"loader": {
"create_object": true,
"module": "llama_index.core",
"class": "SimpleDirectoryReader",
"init_args": {
"input_dir": "../../docs/sphinx_doc/en/source/tutorial",
"required_exts": [
".md"
]
}
}
}
},
{
"load_data": {
"loader": {
"create_object": true,
"module": "llama_index.core",
"class": "SimpleDirectoryReader",
"init_args": {
"input_dir": "../../src/agentscope",
"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
}
}
]
}
}
]
}
]

View File

@@ -0,0 +1,14 @@
[
{
"model_type": "dashscope_text_embedding",
"config_name": "qwen_emb_config",
"model_name": "text-embedding-v2",
"api_key": ""
},
{
"model_type": "dashscope_chat",
"config_name": "qwen_config",
"model_name": "qwen-max",
"api_key": ""
}
]

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
""" Group chat utils."""
import re
from typing import Sequence
def select_next_one(agents: Sequence, rnd: int) -> Sequence:
"""
Select next agent.
"""
return agents[rnd % len(agents)]
def filter_agents(string: str, agents: Sequence) -> Sequence:
"""
This function filters the input string for occurrences of the given names
prefixed with '@' and returns a list of the found names.
"""
if len(agents) == 0:
return []
# Create a pattern that matches @ followed by any of the candidate names
pattern = (
r"@(" + "|".join(re.escape(agent.name) for agent in agents) + r")\b"
)
# Find all occurrences of the pattern in the string
matches = re.findall(pattern, string)
# Create a dictionary mapping agent names to agent objects for quick lookup
agent_dict = {agent.name: agent for agent in agents}
# Return the list of matched agent objects preserving the order
ordered_agents = [
agent_dict[name] for name in matches if name in agent_dict
]
return ordered_agents

View File

@@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
"""
An example for conversation between user and agents with RAG capability.
One agent is a tutorial assistant, the other is a code explainer.
"""
import json
import os
from groupchat_utils import filter_agents
import agentscope
from agentscope.agents import UserAgent
from agentscope.rag import KnowledgeBank
AGENT_CHOICE_PROMPT = """
There are following available agents. You need to choose the most appropriate
agent(s) to answer the user's question.
agent descriptions:{}
First, rephrase the user's question, which must contain the key information.
The you need to think step by step. If you believe some of the agents are
good candidates to answer the question (e.g., AGENT_1 and AGENT_2), then
you need to follow the following format to generate your output:
'
Because $YOUR_REASONING.
I believe @AGENT_1 and @AGENT_2 are the most appropriate agents to answer
your question.
'
"""
def prepare_docstring_html() -> None:
"""prepare docstring in html for API assistant"""
if not os.path.exists("../../docs/docstring_html/"):
os.system(
"sphinx-apidoc -f -o ../../docs/sphinx_doc/en/source "
"../../src/agentscope -t template",
)
os.system(
"sphinx-build -b html ../../docs/sphinx_doc/en/source "
"../../docs/docstring_html/ -W --keep-going",
)
def main() -> None:
"""A RAG multi-agent demo"""
# prepare html for api agent
prepare_docstring_html()
# prepare models
with open("configs/model_config.json", "r", encoding="utf-8") as f:
model_configs = json.load(f)
# load config of the agents
with open("configs/agent_config.json", "r", encoding="utf-8") as f:
agent_configs = json.load(f)
agent_list = agentscope.init(
model_configs=model_configs,
agent_configs=agent_configs,
project="Conversation with RAG agents",
)
rag_agent_list = agent_list[:4]
guide_agent = agent_list[4]
# the knowledge bank can be configured by loading config file
knowledge_bank = KnowledgeBank(configs="configs/knowledge_config.json")
# alternatively, we can easily input the configs to add data to RAG
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"],
},
)
# let knowledgebank to equip rag agent with a (set of) knowledge
# corresponding to its knowledge_id_list
for agent in rag_agent_list:
knowledge_bank.equip(agent, agent.knowledge_id_list)
# an alternative way is to provide knowledge list to agents
# when initializing them one by one, e.g.
#
# ```
# 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,
# )
# ```
rag_agent_names = [agent.name for agent in rag_agent_list]
# update guide agent system prompt with the descriptions of rag agents
rag_agent_descriptions = [
"agent name: "
+ agent.name
+ "\n agent description"
+ agent.description
+ "\n"
for agent in rag_agent_list
]
guide_agent.sys_prompt = (
guide_agent.sys_prompt
+ AGENT_CHOICE_PROMPT.format(
"".join(rag_agent_descriptions),
)
)
user_agent = UserAgent()
while True:
# The workflow is the following:
# 1. user input a message,
# 2. if it mentions (@) one of the agents, the agent will be called
# 3. otherwise, the guide agent will decide which agent to call
# 4. the called agent will respond to the user
# 5. repeat
x = user_agent()
x.role = "user" # to enforce dashscope requirement on roles
if len(x["content"]) == 0 or str(x["content"]).startswith("exit"):
break
speak_list = filter_agents(x.get("content", ""), rag_agent_list)
if len(speak_list) == 0:
guide_response = guide_agent(x)
# Only one agent can be called in the current version,
# we may support multi-agent conversation later
speak_list = filter_agents(
guide_response.get("content", ""),
rag_agent_list,
)
agent_name_list = [agent.name for agent in speak_list]
for agent_name, agent in zip(agent_name_list, speak_list):
if agent_name in rag_agent_names:
agent(x)
if __name__ == "__main__":
main()

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,54 @@
# Conversation with gpt-4o (OpenAI Vision Model)
This example will show
- How to use gpt-4o and other OpenAI vision models in AgentScope
In this example,
- you can have a conversation with OpenAI vision models.
- you can show gpt-4o with your drawings or web ui designs and look for its suggestions.
- you can share your pictures with gpt-4o and ask for its comments,
Just input your image url (both local and web URLs are supported) and talk with gpt-4o.
## Background
In May 13, 2024, OpenAI released their new model, gpt-4o, which is a large multimodal model that can process both text and multimodal data.
## Tested Models
The following models are tested in this example. For other models, some modifications may be needed.
- gpt-4o
- gpt-4-turbo
- gpt-4-vision
## Prerequisites
You need to satisfy the following requirements to run this example.
- Install the latest version of AgentScope by
```bash
git clone https://github.com/modelscope/agentscope.git
cd agentscope
pip install -e .
```
- Prepare an OpenAI API key
## Running the Example
First fill your OpenAI API key in `conversation_with_gpt-4o.py`, then execute the following command to run the conversation with gpt-4o.
```bash
python conversation_with_gpt-4o.py
```
## A Running Example
- Conversation history with gpt-4o.
<img src="https://img.alicdn.com/imgextra/i4/O1CN01oQHcmy1mHXALklkMe_!!6000000004929-2-tps-5112-1276.png" alt="conversation history"/>
- My picture
<img src="https://img.alicdn.com/imgextra/i3/O1CN01UpQaLN27hjidUipMv_!!6000000007829-0-tps-720-1280.jpg" alt="my picture" width="200" />

View File

@@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
"""An example for conversation with OpenAI vision models, especially for
GPT-4o."""
import agentscope
from agentscope.agents import UserAgent, DialogAgent
# Fill in your OpenAI API key
YOUR_OPENAI_API_KEY = "xxx"
model_config = {
"config_name": "gpt-4o_config",
"model_type": "openai_chat",
"model_name": "gpt-4o",
"api_key": YOUR_OPENAI_API_KEY,
"generate_args": {
"temperature": 0.7,
},
}
agentscope.init(
model_configs=model_config,
project="Conversation with GPT-4o",
)
# Require user to input URL, and press enter to skip the URL input
user = UserAgent("user", require_url=True)
agent = DialogAgent(
"Friday",
sys_prompt="You're a helpful assistant named Friday.",
model_config_name="gpt-4o_config",
)
x = None
while True:
x = agent(x)
x = user(x)
if x.content == "exit": # type "exit" to break the loop
break

View File

@@ -0,0 +1,27 @@
# Create an Agent with LangChain
AgentScope is a highly flexible multi-agent platform. It allows developers
to create agents with third-party libraries.
In this example, we will show how to create an assistant agent with
LangChain in AgentScope, and interact with user in a conversation.
**Note** we use OpenAI API for LangChain in this example. Developers can
modify it according to their own needs.
## Install LangChain
Before running the example, please install LangChain by the following command:
```bash
pip install langchain==0.1.11 langchain-openai==0.0.8
```
## Create Agent with LangChain
In this example, the memory management, prompt engineering, and model
invocation are all handled by LangChain.
Specifically, we create an agent class named `LangChainAgent`.
In its `reply` function, developers only need parse the input message and
wrap the output message into `agentscope.message.Msg` class.
After that, developers can build the conversation in AgentScope, and the
`LangChainAgent` is the same as other agents in AgentScope.

View File

@@ -0,0 +1,87 @@
# -*- coding: utf-8 -*-
"""A simple example of using langchain to create an assistant agent in
AgentScope."""
import os
from typing import Optional, Union, Sequence
from langchain_openai import OpenAI
from langchain.memory import ConversationBufferMemory
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain
import agentscope
from agentscope.agents import AgentBase
from agentscope.agents import UserAgent
from agentscope.message import Msg
class LangChainAgent(AgentBase):
"""An agent that implemented by langchain."""
def __init__(self, name: str) -> None:
"""Initialize the agent."""
# Disable AgentScope memory and use langchain memory instead
super().__init__(name, use_memory=False)
# [START] BY LANGCHAIN
# Create a memory in langchain
memory = ConversationBufferMemory(memory_key="chat_history")
# Prepare prompt
template = """
You are a helpful assistant, and your goal is to help the user.
{chat_history}
Human: {human_input}
Assistant:"""
prompt = PromptTemplate(
input_variables=["chat_history", "human_input"],
template=template,
)
llm = OpenAI(openai_api_key=os.environ["OPENAI_API_KEY"])
# Prepare a chain and manage the memory by LLMChain in langchain
self.llm_chain = LLMChain(
llm=llm,
prompt=prompt,
verbose=False,
memory=memory,
)
# [END] BY LANGCHAIN
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
# [START] BY LANGCHAIN
# Generate response
response_str = self.llm_chain.predict(human_input=x.content)
# [END] BY LANGCHAIN
# Wrap the response in a message object in AgentScope
return Msg(name=self.name, content=response_str, role="assistant")
# Build a conversation between user and assistant agent
# init AgentScope
agentscope.init(
project="Conversation with LangChain",
)
# Create an instance of the langchain agent
agent = LangChainAgent(name="Assistant")
# Create a user agent from AgentScope
user = UserAgent("User")
msg = None
while True:
# User input
msg = user(msg)
if msg.content == "exit":
break
# Agent speaks
msg = agent(msg)

View File

@@ -0,0 +1,36 @@
###
# Multi-Agent Group Conversation in AgentScope
This example demonstrates a multi-agent group conversation facilitated by AgentScope. The script sets up a virtual chat room where a user agent interacts with several NPC (non-player character) agents. Participants can utilize a special "@" mention functionality to address specific agents directly.
## Background
The conversation takes place in a simulated chat room environment with predefined roles for each participant. Topics are open-ended and evolve based on the user's input and agents' responses.
## Tested Models
These models are tested in this example. For other models, some modifications may be needed.
- gemini_chat (models/gemini-pro, models/gemini-1.0-pro)
- dashscope_chat (qwen-max, qwen-turbo)
- ollama_chat (ollama_llama3_8b)
## Prerequisites
Fill the next cell to meet the following requirements:
- Set your `api_key` in the `configs/model_configs.json` file
- Optional: Launch agentscope gradio with `as_gradio main.py`
## How to Use
1. Run the script using the command: `python main.py`
2. Address specific agents by typing "@" followed by the agent's name.
3. Type "exit" to leave the chat.
## Customization Options
You can adjust the behavior and parameters of the NPC agents and conversation model by editing the `agent_configs.json` and `model_configs.json` files, respectively.
### Changing User Input Time Limit
Adjust the `USER_TIME_TO_SPEAK` variable in the `main.py` script to change the time limit for user input.
###

View File

@@ -0,0 +1,29 @@
[
{
"class": "DialogAgent",
"args": {
"name": "Lingfeng",
"sys_prompt":"You are Lingfeng, a noble in the imperial court, known for your wisdom and strategic acumen. You often engage in complex political intrigues and have recently suspected the Queens adviser of treachery. Your speaking style is reminiscent of classical literature.",
"model_config_name": "gpt-4",
"use_memory": true
}
},
{
"class": "DialogAgent",
"args": {
"name": "Boyu",
"sys_prompt":"You are Boyu, a friend of Lingfeng and an enthusiast of court dramas. Your speech is modern but with a flair for the dramatic, matching your love for emotive storytelling. You've been closely following Lingfengs political maneuvers in the imperial court through secret correspondence.",
"model_config_name": "gpt-4",
"use_memory": true
}
},
{
"class": "DialogAgent",
"args": {
"name": "Haotian",
"sys_prompt":"You are Haotian, Lingfengs cousin who prefers the open fields to the confines of court life. As a celebrated athlete, your influence has protected Lingfeng in times of political strife. You promote physical training as a way to prepare for life's battles, often using sports metaphors in conversation.",
"model_config_name": "gpt-4",
"use_memory": true
}
}
]

View File

@@ -0,0 +1,19 @@
[
{
"model_type": "openai_chat",
"config_name": "gpt-4",
"model_name": "gpt-4",
"api_key": "xxx",
"organization": "xxx",
"generate_args": {
"temperature": 0.5
}
},
{
"model_type": "post_api_chat",
"config_name": "my_post_api",
"api_url": "https://xxx",
"headers": {},
"json_args": {}
}
]

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
""" Group chat utils."""
import re
from typing import Sequence
def select_next_one(agents: Sequence, rnd: int) -> Sequence:
"""
Select next agent.
"""
return agents[rnd % len(agents)]
def filter_agents(string: str, agents: Sequence) -> Sequence:
"""
This function filters the input string for occurrences of the given names
prefixed with '@' and returns a list of the found names.
"""
if len(agents) == 0:
return []
# Create a pattern that matches @ followed by any of the candidate names
pattern = (
r"@(" + "|".join(re.escape(agent.name) for agent in agents) + r")\b"
)
# Find all occurrences of the pattern in the string
matches = re.findall(pattern, string)
# Create a dictionary mapping agent names to agent objects for quick lookup
agent_dict = {agent.name: agent for agent in agents}
# Return the list of matched agent objects preserving the order
ordered_agents = [
agent_dict[name] for name in matches if name in agent_dict
]
return ordered_agents

View File

@@ -0,0 +1,79 @@
# -*- coding: utf-8 -*-
""" A group chat where user can talk any time implemented by agentscope. """
from loguru import logger
from groupchat_utils import (
select_next_one,
filter_agents,
)
import agentscope
from agentscope.agents import UserAgent
from agentscope.message import Msg
from agentscope.msghub import msghub
USER_TIME_TO_SPEAK = 10
DEFAULT_TOPIC = """
This is a chat room and you can speak freely and briefly.
"""
SYS_PROMPT = """
You can designate a member to reply to your message, you can use the @ symbol.
This means including the @ symbol in your message, followed by
that person's name, and leaving a space after the name.
All participants are: {agent_names}
"""
def main() -> None:
"""group chat"""
npc_agents = agentscope.init(
model_configs="./configs/model_configs.json",
agent_configs="./configs/agent_configs.json",
project="Conversation with Mentions",
)
user = UserAgent()
agents = list(npc_agents) + [user]
hint = Msg(
name="Host",
content=DEFAULT_TOPIC
+ SYS_PROMPT.format(
agent_names=[agent.name for agent in agents],
),
role="assistant",
)
rnd = 0
speak_list = []
with msghub(agents, announcement=hint):
while True:
try:
x = user(timeout=USER_TIME_TO_SPEAK)
if x.content == "exit":
break
except TimeoutError:
x = {"content": ""}
logger.info(
f"User has not typed text for "
f"{USER_TIME_TO_SPEAK} seconds, skip.",
)
speak_list += filter_agents(x.get("content", ""), npc_agents)
if len(speak_list) > 0:
next_agent = speak_list.pop(0)
x = next_agent()
else:
next_agent = select_next_one(npc_agents, rnd)
x = next_agent()
speak_list += filter_agents(x.content, npc_agents)
rnd += 1
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,90 @@
# -*- coding: utf-8 -*-
"""An example of a conversation with a ReAct agent."""
import sys
import io
from agentscope.agents import UserAgent
from agentscope.agents.react_agent import ReActAgent
from agentscope.service import (
bing_search, # or google_search,
read_text_file,
write_text_file,
ServiceToolkit,
ServiceResponse,
ServiceExecStatus,
)
import agentscope
# Prepare the Bing API key and model configuration
BING_API_KEY = "{YOUR_BING_API_KEY}"
YOUR_MODEL_CONFIGURATION_NAME = "{YOUR_MODEL_CONFIGURATION_NAME}"
YOUR_MODEL_CONFIGURATION = {
"model_type": "xxx",
"config_name": YOUR_MODEL_CONFIGURATION_NAME,
# ...
}
# Prepare a new tool function
def execute_python_code(code: str) -> ServiceResponse: # pylint: disable=C0301
"""
Execute Python code and capture the output. Note you must `print` the output to get the result.
Args:
code (`str`):
The Python code to be executed.
""" # noqa
# Create a StringIO object to capture the output
old_stdout = sys.stdout
new_stdout = io.StringIO()
sys.stdout = new_stdout
try:
# Using `exec` to execute code
exec(code)
except Exception as e:
# If an exception occurs, capture the exception information
output = str(e)
status = ServiceExecStatus.ERROR
else:
# If the execution is successful, capture the output
output = new_stdout.getvalue()
status = ServiceExecStatus.SUCCESS
finally:
# Recover the standard output
sys.stdout = old_stdout
# Wrap the output and status into a ServiceResponse object
return ServiceResponse(status, output)
# Prepare the tools for the agent
service_toolkit = ServiceToolkit()
service_toolkit.add(bing_search, api_key=BING_API_KEY, num_results=3)
service_toolkit.add(execute_python_code)
service_toolkit.add(read_text_file)
service_toolkit.add(write_text_file)
agentscope.init(
model_configs=YOUR_MODEL_CONFIGURATION,
project="Conversation with ReActAgent",
)
# Create agents
agent = ReActAgent(
name="assistant",
model_config_name=YOUR_MODEL_CONFIGURATION_NAME,
verbose=True,
service_toolkit=service_toolkit,
)
user = UserAgent(name="User")
# Build
x = None
while True:
x = user(x)
if x.content == "exit":
break
x = agent(x)

View File

@@ -0,0 +1,576 @@
{
"cells": [
{
"cell_type": "markdown",
"source": [
"# Conversation with a ReAct Agent\n",
"\n",
"In this example, we will show you\n",
"- How to create a new service function for the tool agent to use\n",
"- How to use the ServiceToolkit module pre-process the tool functions for LLMs\n",
"- How to use the built-in ReAct agent to solve a problem\n",
"\n",
"## Prerequisites\n",
"\n",
"- Follow [READMD.md](https://github.com/modelscope/agentscope) to install AgentScope. \n",
"- Prepare a model configuration. AgentScope supports both local deployed model services (CPU or GPU) and third-party services. More details and example model configurations please refer to our [tutorial](https://modelscope.github.io/agentscope/en/tutorial/203-model.html).\n",
"- [Optional] A bing (or google) search API key is suggested to experience the web search function. Here we take bing search as an example. \n",
"\n",
"## Note\n",
"\n",
"- The example is tested with the following models. For other models, you may need to adjust the prompt.\n",
" - gpt-4\n",
" - gpt-3.5-turbo\n",
" - qwen-max"
],
"metadata": {
"collapsed": false
},
"id": "6b868755498c634a"
},
{
"cell_type": "code",
"execution_count": 1,
"outputs": [],
"source": [
"YOUR_MODEL_CONFIGURATION_NAME = \"{YOUR_MODEL_CONFIGURATION_NAME}\"\n",
"\n",
"YOUR_MODEL_CONFIGURATION = {\n",
" \"model_type\": \"xxx\", \n",
" \"config_name\": YOUR_MODEL_CONFIGURATION_NAME\n",
" \n",
" # ...\n",
"}\n",
"\n",
"BING_API_KEY = \"{BING_API_KEY}\""
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-04-16T01:51:29.449020Z",
"start_time": "2024-04-16T01:51:29.435200Z"
}
},
"id": "8355a19d4eb5af55"
},
{
"cell_type": "markdown",
"source": [
"## Step 1: Customize a Service Function\n",
"\n",
"Taking `execute_python_code` as an example, we will show how to create a new service function that can be processed by `ServiceToolkit` module.\n",
"\n",
"In AgentScope, a service function should have the following structure:\n",
"\n",
"- A well formatted docstring (Google style is recommended), which contains\n",
" - A brief description of function in docstring.\n",
" - A brief description of the input arguments.\n",
" - Wrap the output and execution status into a `ServiceResponse` object.\n",
"\n",
"The following is a simple example of a service function that executes Python code and captures the output. (Also, you can use the `execute_python_code` function in the `agentscope.service` module directly, which provide more features.)\""
],
"metadata": {
"collapsed": false
},
"id": "8ef2e3639e08cc0a"
},
{
"cell_type": "code",
"execution_count": 2,
"outputs": [],
"source": [
"from agentscope.service import ServiceResponse, ServiceExecStatus\n",
"import sys, io\n",
"\n",
"def execute_python_code(code: str) -> ServiceResponse:\n",
" \"\"\"\n",
" Execute Python code and capture the output. Note you must `print` the output to get the result.\n",
" Args:\n",
" code (`str`):\n",
" The Python code to be executed.\n",
" \"\"\" # noqa\n",
"\n",
" # Create a StringIO object to capture the output\n",
" old_stdout = sys.stdout\n",
" new_stdout = io.StringIO()\n",
" sys.stdout = new_stdout\n",
"\n",
" try:\n",
" # Using `exec` to execute code\n",
" exec(code)\n",
" except Exception as e:\n",
" # If an exception occurs, capture the exception information\n",
" output = str(e)\n",
" status = ServiceExecStatus.ERROR\n",
" else:\n",
" # If the execution is successful, capture the output\n",
" output = new_stdout.getvalue()\n",
" status = ServiceExecStatus.SUCCESS\n",
" finally:\n",
" # Recover the standard output\n",
" sys.stdout = old_stdout\n",
"\n",
" # Wrap the output and status into a ServiceResponse object\n",
" return ServiceResponse(status, output)"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-04-16T01:51:38.238708Z",
"start_time": "2024-04-16T01:51:36.965973Z"
}
},
"id": "9d34e13ea408f21d"
},
{
"cell_type": "markdown",
"source": [
"After defining the service function, we try to use `ServiceToolkit` to pre-process the tool functions for LLMs. Here, we take `execute_python_code` function as example and observe the JSON schema generated by `service_toolkit`."
],
"metadata": {
"collapsed": false
},
"id": "a97eab8ecc98ed89"
},
{
"cell_type": "code",
"execution_count": 3,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"{\n",
" \"execute_python_code\": {\n",
" \"type\": \"function\",\n",
" \"function\": {\n",
" \"name\": \"execute_python_code\",\n",
" \"description\": \"Execute Python code and capture the output. Note you must `print` the output to get the result.\",\n",
" \"parameters\": {\n",
" \"type\": \"object\",\n",
" \"properties\": {\n",
" \"code\": {\n",
" \"type\": \"string\",\n",
" \"description\": \"The Python code to be executed.\"\n",
" }\n",
" },\n",
" \"required\": [\n",
" \"code\"\n",
" ]\n",
" }\n",
" }\n",
" }\n",
"}\n"
]
}
],
"source": [
"from agentscope.service import ServiceToolkit\n",
"\n",
"service_toolkit = ServiceToolkit()\n",
"service_toolkit.add(execute_python_code)\n",
"\n",
"import json\n",
"print(json.dumps(service_toolkit.json_schemas, indent=4))"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-04-16T01:51:43.581845Z",
"start_time": "2024-04-16T01:51:43.578326Z"
}
},
"id": "1ef000177bccd2a5"
},
{
"cell_type": "markdown",
"source": [
"## Step 2: Prepare all tool functions for the agent\n",
"Similar as above, we can create different service functions, or use the built-in functions in `agentscope.service` module as follows. More service functions can be found under the `agentscope.service` module."
],
"metadata": {
"collapsed": false
},
"id": "ebd210b104459fb5"
},
{
"cell_type": "code",
"execution_count": 4,
"outputs": [],
"source": [
"from agentscope.service import (\n",
" bing_search, # or google_search,\n",
" read_text_file,\n",
" write_text_file, \n",
")\n",
"\n",
"# Deal with arguments that need to be input by developers\n",
"service_toolkit.add(bing_search, api_key=BING_API_KEY, num_results=3)\n",
"service_toolkit.add(read_text_file)\n",
"service_toolkit.add(write_text_file)"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-04-16T01:52:00.399308Z",
"start_time": "2024-04-16T01:52:00.393475Z"
}
},
"id": "9c7f4d40bfab5003"
},
{
"cell_type": "markdown",
"source": [
"## Step 3: Create a React Agent"
],
"metadata": {
"collapsed": false
},
"id": "794e63922cd0c954"
},
{
"cell_type": "markdown",
"source": [
"A ReAct Agent is built in AgentScope to show how to construct complex reasoning processes. It solves a problem by a loop of \"thought\", \"action\" and \"observation\" steps. \n",
"\n",
"- thought: analyze and decide next action (the tools to use)\n",
"- action: execute the action (the tool function)\n",
"- observation: observe the execution results\n",
"\n",
"In AgentScope, we implement the above steps in a `ReActAgent` class. Let's first taking a look at its system prompt. "
],
"metadata": {
"collapsed": false
},
"id": "3f2e4ad0458966cd"
},
{
"cell_type": "code",
"execution_count": 5,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"2024-04-16 09:52:31.694 | INFO | agentscope.models:read_model_configs:181 - Load configs for model wrapper: qwen, gpt-4, ollama\n",
"2024-04-16 09:52:31.711 | INFO | agentscope.utils.monitor:_create_monitor_table:341 - Init [monitor_metrics] as the monitor table\n",
"2024-04-16 09:52:31.712 | INFO | agentscope.utils.monitor:_create_monitor_table:342 - Init [monitor_metrics_quota_exceeded] as the monitor trigger\n",
"2024-04-16 09:52:31.712 | INFO | agentscope.utils.monitor:__init__:311 - SqliteMonitor initialization completed at [./runs/run_20240416-095137_jgmih8/agentscope.db]\n",
"2024-04-16 09:52:31.716 | INFO | agentscope.models.model:__init__:200 - Initialize model by configuration [qwen]\n",
"2024-04-16 09:52:31.718 | INFO | agentscope.utils.monitor:register:362 - Register metric [qwen-max.call_counter] to SqliteMonitor with unit [times] and quota [None]\n",
"2024-04-16 09:52:31.719 | INFO | agentscope.utils.monitor:register:362 - Register metric [qwen-max.prompt_tokens] to SqliteMonitor with unit [token] and quota [None]\n",
"2024-04-16 09:52:31.721 | INFO | agentscope.utils.monitor:register:362 - Register metric [qwen-max.completion_tokens] to SqliteMonitor with unit [token] and quota [None]\n",
"2024-04-16 09:52:31.722 | INFO | agentscope.utils.monitor:register:362 - Register metric [qwen-max.total_tokens] to SqliteMonitor with unit [token] and quota [None]\n",
"################################################################################\n",
"You're a helpful assistant. Your name is assistant.\n",
"\n",
"## Tool Functions:\n",
"The following tool functions are available in the format of\n",
"```\n",
"{index}. {function name}: {function description}\n",
"{argument1 name} ({argument type}): {argument description}\n",
"{argument2 name} ({argument type}): {argument description}\n",
"...\n",
"```\n",
"\n",
"1. execute_python_code: Execute Python code and capture the output. Note you must `print` the output to get the result.\n",
"\tcode (string): The Python code to be executed.\n",
"2. bing_search: Search question in Bing Search API and return the searching results\n",
"\tquestion (string): The search query string.\n",
"3. read_text_file: Read the content of the text file.\n",
"\tfile_path (string): The path to the text file to be read.\n",
"4. write_text_file: Write content to a text file.\n",
"\tfile_path (string): The path to the file where content will be written.\n",
"\toverwrite (boolean): Whether to overwrite the file if it already exists.\n",
"\tcontent (string): Content to write into the file.\n",
"\n",
"## What You Should Do:\n",
"1. First, analyze the current situation, and determine your goal.\n",
"2. Then, check if your goal is already achieved. If so, try to generate a response. Otherwise, think about how to achieve it with the help of provided tool functions.\n",
"3. Respond in the required format.\n",
"\n",
"## Note:\n",
"1. Fully understand the tool functions and their arguments before using them.\n",
"2. You should decide if you need to use the tool functions, if not then return an empty list in \"function\" field.\n",
"3. Make sure the types and values of the arguments you provided to the tool functions are correct.\n",
"4. Don't take things for granted. For example, where you are, what's the time now, etc. You can try to use the tool functions to get information.\n",
"5. If the function execution fails, you should analyze the error and try to solve it.\n",
"\n",
"################################################################################\n"
]
}
],
"source": [
"from agentscope.agents import ReActAgent\n",
"import agentscope\n",
"\n",
"agentscope.init(\n",
" model_configs=YOUR_MODEL_CONFIGURATION,\n",
" project=\"Conversation with ReActAgent\",\n",
")\n",
"\n",
"agent = ReActAgent(\n",
" name=\"assistant\",\n",
" model_config_name=YOUR_MODEL_CONFIGURATION_NAME,\n",
" service_toolkit=service_toolkit, \n",
" verbose=True, # set verbose to True to show the reasoning process\n",
")\n",
"\n",
"print(\"#\"*80)\n",
"print(agent.sys_prompt)\n",
"print(\"#\"*80)"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-04-16T01:52:31.741951Z",
"start_time": "2024-04-16T01:52:31.675Z"
}
},
"id": "ad793ef9e2bb7945"
},
{
"cell_type": "markdown",
"source": [
"As shown above, the system prompt is consisted of \n",
"\n",
"- the description of the agent\n",
"- the description of tool functions\n",
"- some notices for the large language models\n",
"\n",
"Besides, when ReAct Agent generates \"thought\", we use the following prompt to guide the generation. It's wrapped into a message with `role` field as `\"system\"` to distinguish from the user's input.\n",
"\n",
"```python\n",
"# print(agent.parser.format_instruction)\n",
"\"\"\"\n",
"You should respond a json object in a json fenced code block as follows:\n",
"```json\n",
"{\n",
"\t\"thought\": \"what you thought\", \n",
"\t\"speak\": \"what you speak\", \n",
"\t\"function\": [\n",
"\t\t{\n",
"\t\t\t\"name\": \"{function name}\", \n",
"\t\t\t\"arguments\": {\n",
"\t\t\t\t\"{argument1 name}\": xxx, \n",
"\t\t\t\t\"{argument2 name}\": xxx\n",
"\t\t\t}\n",
"\t\t}\n",
"\t]\n",
"}```\n",
"\"\"\"\n",
"```\n",
"\n",
"When the `function` field in the response dictionary is empty, the agent will end the \"reasoning-acting\" loop and return the final response to the user. \n",
"\n",
"More implementation details please refer to the [source code](../../src/agentscope/agents/react_agent.py)."
],
"metadata": {
"collapsed": false
},
"id": "ca977a86cdd1b482"
},
{
"cell_type": "markdown",
"source": [
"## Step 4: Try to Solve a Problem with the ReAct Agent\n",
"\n",
"Now, let's try to ask the example question in the ReAct Paper: `\"Aside from the Apple Remote, what other device can control the program Apple Remote was originally designed to interact with?\"` as follows"
],
"metadata": {
"collapsed": false
},
"id": "b6e2e7a1c5fac168"
},
{
"cell_type": "code",
"execution_count": 8,
"outputs": [],
"source": [
"from agentscope.message import Msg\n",
"\n",
"msg_question = Msg(\n",
" name=\"user\", \n",
" content=\"Aside from the Apple Remote, what other device can control the program Apple Remote was originally designed to interact with?\", \n",
" role=\"user\"\n",
")\n",
"\n",
"res = agent(msg_question)"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-04-16T02:02:06.982344Z",
"start_time": "2024-04-16T02:00:21.137787Z"
}
},
"id": "be6ffe0c91474589"
},
{
"cell_type": "markdown",
"source": [
"The above example is very interesting! Because the ReAct algorithm is famous and its example question occurs in many different websites, the results in the first searching are all about ReAct algorithm, rather than the answer to the question. \n",
"\n",
"Knowing this, in the reasoning step of the second iteration, the ReAct agent decides to rewrite the query and re-search in the website. It finally finds the answer in the third iteration. \n",
"\n",
"The behavior of the ReAct agent is unexpected and interesting. It shows that the ReAct agent can learn from the previous results and adjust its behavior in the next iteration. \n",
"\n",
"You can use the following code to build a conversation with the ReAct agent, and we also provide [completed code](./code/conversation_with_react_agent.py). \n",
"\n",
"```python\n",
"from agentscope.agents import UserAgent\n",
"\n",
"user = UserAgent(name=\"User\")\n",
"\n",
"x = None\n",
"while True:\n",
" x = user(x)\n",
" if x.content == \"exit\":\n",
" break\n",
" x = agent(x)\n",
"```"
],
"metadata": {
"collapsed": false
},
"id": "602ccd32a8514db7"
},
{
"cell_type": "markdown",
"source": [
"In the end, we print the memory of the agent to show the reasoning process."
],
"metadata": {
"collapsed": false
},
"id": "a1b8d2b3993d36a"
},
{
"cell_type": "code",
"execution_count": 8,
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"[\n",
" {\n",
" \"id\": \"d71ec3345ca3452697d33da946d3725f\",\n",
" \"timestamp\": \"2024-03-26 19:19:57\",\n",
" \"name\": \"system\",\n",
" \"content\": \"You are a helpful assistant.\\n\\nThe following tool functions are available in the format of\\n```\\n{index}. {function name}: {function description}\\n {argument name} ({argument type}): {argument description}\\n ...\\n```\\n\\nTool Functions:\\n1. bing_search: Search question in Bing Search API and return the searching results\\n\\tquestion (string): The search query string.\\n2. execute_python_code: Execute Python code and capture the output. Note you must `print` the output to get the result.\\n\\tcode (string): The Python code to be executed.\\n3. read_text_file: Read the content of the text file.\\n\\tfile_path (string): The path to the text file to be read.\\n4. write_text_file: Write content to a text file.\\n\\toverwrite (boolean): Whether to overwrite the file if it already exists.\\n\\tfile_path (string): The path to the file where content will be written.\\n\\tcontent (string): Content to write into the file.\\n\\nNotice:\\n1. Fully understand the tool function and its arguments before using it.\\n2. Only use the tool function when it's necessary.\\n3. Check if the arguments you provided to the tool function is correct in type and value.\\n4. You can't take some problems for granted. For example, where you are, what's the time now, etc. But you can try to use the tool function to solve the problem.\\n5. If the function execution fails, you should analyze the error and try to solve it.\\n\\n\",\n",
" \"url\": null,\n",
" \"role\": \"system\"\n",
" },\n",
" {\n",
" \"id\": \"2a43c07ef95644aa9eb25b28731cd65c\",\n",
" \"timestamp\": \"2024-03-26 19:19:57\",\n",
" \"name\": \"user\",\n",
" \"content\": \"Aside from the Apple Remote, what other device can control the program Apple Remote was originally designed to interact with?\",\n",
" \"url\": null,\n",
" \"role\": \"user\"\n",
" },\n",
" {\n",
" \"id\": \"4a25ccc466f34653ac6e6acb86fc24d6\",\n",
" \"timestamp\": \"2024-03-26 19:20:02\",\n",
" \"name\": \"assistant\",\n",
" \"content\": {\n",
" \"thought\": \"I need to search for information about what other devices can control the program that the Apple Remote was originally designed to interact with.\",\n",
" \"speak\": \"Let me find that information for you.\",\n",
" \"function\": [\n",
" {\n",
" \"name\": \"bing_search\",\n",
" \"arguments\": {\n",
" \"question\": \"What devices can control the program Apple Remote was designed for?\"\n",
" }\n",
" }\n",
" ]\n",
" },\n",
" \"url\": null,\n",
" \"role\": \"assistant\"\n",
" },\n",
" {\n",
" \"id\": \"253d8cc15548499e97259d5fedde92f7\",\n",
" \"timestamp\": \"2024-03-26 19:20:03\",\n",
" \"name\": \"system\",\n",
" \"content\": \"Execution Results:\\n1. bing_search:\\n [EXECUTE STATUS]: SUCCESS\\n [EXECUTE RESULT]: [{'title': '《ReAct: SYNERGIZING REASONING AND ACTING IN ...', 'link': 'https://www.cnblogs.com/LittleHann/p/17541295.html', 'snippet': 'Aside from the Apple Remote, what other devices can control the program Apple Remote was originally designed to interact with? 我们可以看到该模型生成了任务解决轨迹(思考、行动)。'}, {'title': 'REACT在大模型中协同推理与行动使其能够解决推理和 ...', 'link': 'https://baijiahao.baidu.com/s?id=1775590643651365661', 'snippet': 'QAside from the Apple Remote, what other device can control the program Apple Remote was originally designed to intect with? 除了苹果遥控器之外,还有哪些设备可以控制最初设计用于连接的苹果遥控器所配合的程序?'}, {'title': 'ReAct Prompting | Prompt Engineering Guide', 'link': 'https://www.promptingguide.ai/techniques/react', 'snippet': 'Aside from the Apple Remote, what other devices can control the program Apple Remote was originally designed to interact with? Note that in-context examples are also added to the prompt but we exclude that here for simplicity.'}]\\n\",\n",
" \"url\": null,\n",
" \"role\": \"system\"\n",
" },\n",
" {\n",
" \"id\": \"2346ccec579b48848a669d9f16b53c49\",\n",
" \"timestamp\": \"2024-03-26 19:20:10\",\n",
" \"name\": \"assistant\",\n",
" \"content\": {\n",
" \"thought\": \"The search results did not provide a clear answer to the question. I need to refine the search query to get more specific results.\",\n",
" \"speak\": \"I'm sorry, I couldn't find a clear answer. Let me try a different search.\",\n",
" \"function\": [\n",
" {\n",
" \"name\": \"bing_search\",\n",
" \"arguments\": {\n",
" \"question\": \"What devices can control Apple TV other than Apple Remote?\"\n",
" }\n",
" }\n",
" ]\n",
" },\n",
" \"url\": null,\n",
" \"role\": \"assistant\"\n",
" },\n",
" {\n",
" \"id\": \"67d707e4c0d44751aae5c5bbbe978f16\",\n",
" \"timestamp\": \"2024-03-26 19:20:11\",\n",
" \"name\": \"system\",\n",
" \"content\": \"Execution Results:\\n1. bing_search:\\n [EXECUTE STATUS]: SUCCESS\\n [EXECUTE RESULT]: [{'title': 'Use other remotes to control Apple TV - Apple Support', 'link': 'https://support.apple.com/guide/tv/use-other-remotes-atvb0410f604/tvos', 'snippet': 'Table of Contents. Use other remotes to control Apple TV. You can use a supported TV or receiver remote, a network-based remote for home-control systems, or an infrared remote (commonly known as a universal remote) to control Apple TV.'}, {'title': '4 Siri Remote Alternatives You Can Use to Control Your Apple TV - MUO', 'link': 'https://www.makeuseof.com/alternative-apple-tv-remotes/', 'snippet': \\\"Fortunately, if you find yourself looking for alternatives to control your Apple TV, there are several good options for you. You can use other Apple devices to operate the Apple TV, or even affordable Bluetooth accessories. We'll highlight the best alternatives to the Apple TV remote. 1. Use Your iPhone or iPad to Control the Apple TV\\\"}, {'title': 'Apple TV Siri Remote: How to control TVs and receivers - 9to5Mac', 'link': 'https://9to5mac.com/2021/05/27/apple-tv-siri-remote-control-tv-receiver/', 'snippet': 'The old Siri Remote and Apple TV Remote also feature this functionality, but without a dedicated power button, its less convenient and intuitive to use. Another benefit of HDMI-CEC is that...'}]\\n\",\n",
" \"url\": null,\n",
" \"role\": \"system\"\n",
" },\n",
" {\n",
" \"id\": \"7c36f98d1c204a03941375bef1eb44ee\",\n",
" \"timestamp\": \"2024-03-26 19:20:19\",\n",
" \"name\": \"assistant\",\n",
" \"content\": {\n",
" \"thought\": \"The search results provided a clear answer to the question. Other than the Apple Remote, you can use a supported TV or receiver remote, a network-based remote for home-control systems, an infrared remote (commonly known as a universal remote), or other Apple devices like an iPhone or iPad to control the Apple TV.\",\n",
" \"speak\": \"Other than the Apple Remote, you can use a supported TV or receiver remote, a network-based remote for home-control systems, an infrared remote (commonly known as a universal remote), or other Apple devices like an iPhone or iPad to control the Apple TV.\"\n",
" },\n",
" \"url\": null,\n",
" \"role\": \"assistant\"\n",
" }\n",
"]\n"
]
}
],
"source": [
"print(json.dumps(agent.memory.get_memory(), indent=4, ensure_ascii=False))"
],
"metadata": {
"collapsed": false,
"ExecuteTime": {
"end_time": "2024-03-26T12:08:33.469827Z",
"start_time": "2024-03-26T12:08:33.456020Z"
}
},
"id": "5e099490946662f1"
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 2
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython2",
"version": "2.7.6"
}
},
"nbformat": 4,
"nbformat_minor": 5
}

View File

@@ -0,0 +1,291 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Conversation with Software Engineering Agent\n",
"\n",
"SWE-agent(SoftWare Engineering Agent) is an agent designed for solving real world software engineering problems, such as fixing github issues.\n",
"More details can be found in the project's [homepage](https://swe-agent.com/) and related [github repo](https://swe-agent.com/).\n",
"\n",
"In the example here, we partially implement the SWE-agent, and provide a simple example of how to use the implemented SWE-agent to fix a bug in a python file.\n",
"You should note that currently how to enable agents with stronger programming capabilities remains an open challenge, and the performance of the paritially implemented SWE-agent is not guaranteed.\n",
"\n",
"## Prerequisites\n",
"\n",
"- Follow [READMD.md](https://github.com/modelscope/agentscope) to install AgentScope. We require the lastest version, so you should build from source by running `pip install -e .` instead of intalling from pypi. \n",
"- Prepare a model configuration. AgentScope supports both local deployed model services (CPU or GPU) and third-party services. More details and example model configurations please refer to our [tutorial](https://modelscope.github.io/agentscope/en/tutorial/203-model.html).\n",
"- Understand the ServiceToolkit module and how to use it to pre-process the tool functions for LLMs. You can refer to the [ReAct agent example](../conversation_with_react_agent/main.ipynb) and you should also refer to the [tutorial](https://modelscope.github.io/agentscope/en/tutorial/204-service.html) for service functions.\n",
"\n",
"\n",
"## Note\n",
"\n",
"- The example is tested with the following models. For other models, you may need to adjust the prompt.\n",
" - gpt-4\n",
" - dashscope_chat (qwen-max)\n",
" - gemini_chat (gemini-pro)\n",
" - ollama_chat (llama3_8b)\n",
"- How to enable agents with stronger programming capabilities remains an open challenge, and the current implementations are not perfect. Please feel free to explore it yourself."
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [],
"source": [
"YOUR_MODEL_CONFIGURATION_NAME = \"{YOUR_MODEL_CONFIGURATION_NAME}\"\n",
"\n",
"YOUR_MODEL_CONFIGURATION = {\n",
" \"model_type\": \"xxx\", \n",
" \"config_name\": YOUR_MODEL_CONFIGURATION_NAME\n",
" \n",
" # ...\n",
"}"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 1: Initialize the AgentScope environment and SWE-agent\n",
"\n",
"Here we init the agentscope environment and load the SWE-agent.\n",
"\n",
"The code of SWE-agent is in `swe_agent.py`, and the related prompts are in `swe_agent_prompts.py`.\n",
"\n",
"If you are interested in the details, please refer to the code and the origianl SWE-agent repo [here](https://github.com/princeton-nlp/SWE-agent)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from swe_agent import SWEAgent\n",
"\n",
"import agentscope\n",
"\n",
"agentscope.init(\n",
" model_configs=YOUR_MODEL_CONFIGURATION,\n",
" project=\"Conversation with SWE-agent\",\n",
")\n",
"\n",
"agent = SWEAgent(\n",
" name=\"assistant\",\n",
" model_config_name=YOUR_MODEL_CONFIGURATION_NAME,\n",
")\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 2: Create the code to be processed by the SWE-agent\n",
"\n",
"Here we use the `write_file` function to write the following code into `gcd.py`.\n",
"The code here is a wrong implementation of the [Greatest Common Divisor (GCD) algorithm](https://en.wikipedia.org/wiki/Euclidean_algorithm).\n",
"We will ask the SWE-agent to correct it in our next step."
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'status': <ServiceExecStatus.SUCCESS: 1>,\n",
" 'content': 'WRITE OPERATION:\\nYou have written to \"gcd.py\" on these lines: 0:-1.\\ndef gcd(a, b):\\n if a == 0:\\n return b\\n while a != 0:\\n a, b = b, a\\n return b\\n\\ndef lcm(a, b):\\n return (a * b) // gcd(a, b)\\n\\n# testing on GCD and LCM functions\\nprint(\"GCD of 12 and 18 is:\", gcd(12, 18))\\nprint(\"LCM of 12 and 18 is:\", lcm(12, 18))\\n\\n'}"
]
},
"execution_count": 3,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"from swe_agent_service_func import write_file\n",
"\n",
"# here the gcd code is written in a wrong way\n",
"wrong_gcd_text = \"\"\"\n",
"def gcd(a, b):\n",
" if a == 0:\n",
" return b\n",
" while a != 0:\n",
" a, b = b, a\n",
" return b\n",
"\n",
"def lcm(a, b):\n",
" return (a * b) // gcd(a, b)\n",
"\n",
"# testing on GCD and LCM functions\n",
"print(\"GCD of 12 and 18 is:\", gcd(12, 18))\n",
"print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n",
"\"\"\"\n",
"\n",
"write_file(\"gcd.py\", wrong_gcd_text)\n"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Step 3: Ask the SWE-agent to correct the code\n",
"\n",
"Here we simple ask the SWE-agent to correct the code in `gcd.py`."
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"user: The file in gcd.py is not returning the correct answer, can you fix the file for me?\n",
"assistant: {\n",
" \"thought\": \"First thing I'll do is read the content of gcd.py file to understand the existing logic.\",\n",
" \"action\": {\n",
" \"name\": \"read_file\",\n",
" \"arguments\": {\n",
" \"file_path\": \"gcd.py\"\n",
" }\n",
" }\n",
"}\n",
"assistant: \n",
"====Observation====\n",
"\n",
"def gcd(a, b):\n",
" if a == 0:\n",
" return b\n",
" while a != 0:\n",
" a, b = b, a\n",
" return b\n",
"\n",
"def lcm(a, b):\n",
" return (a * b) // gcd(a, b)\n",
"\n",
"# testing on GCD and LCM functions\n",
"print(\"GCD of 12 and 18 is:\", gcd(12, 18))\n",
"print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n",
"\n",
"\n",
"assistant: {\n",
" \"thought\": \"I can see the error in the gcd calculation. The gcd calculation in the file is incorrect. The correct way to find the gcd of two numbers using the Euclidean algorithm is to continually reduce a and b by finding the remainder of a % b until b is 0, at which point a will be the gcd of the original a and b values. In the given code, instead of finding the remainder, it is incorrectly swapping the values of a and b. I'll correct this error by modifying the gcd function using the write_file command, reflecting the correct Euclidean algorithm.\",\n",
" \"action\": {\n",
" \"name\": \"write_file\",\n",
" \"arguments\": {\n",
" \"start_line\": 0,\n",
" \"end_line\": 15,\n",
" \"content\": \"def gcd(a, b):\\n while b != 0:\\n a, b = b, a % b\\n return a\\n\\n\\ndef lcm(a, b):\\n return (a * b) // gcd(a, b)\\n\\n\\n# testing on GCD and LCM functions\\nprint(\\\"GCD of 12 and 18 is:\\\", gcd(12, 18))\\nprint(\\\"LCM of 12 and 18 is:\\\", lcm(12, 18))\\n\",\n",
" \"file_path\": \"gcd.py\"\n",
" }\n",
" }\n",
"}\n",
"assistant: \n",
"====Observation====\n",
"WRITE OPERATION:\n",
"You have written to \"gcd.py\" on these lines: 0:15.def gcd(a, b):\n",
" while b != 0:\n",
" a, b = b, a % b\n",
" return a\n",
"\n",
"\n",
"def lcm(a, b):\n",
" return (a * b) // gcd(a, b)\n",
"\n",
"\n",
"# testing on GCD and LCM functions\n",
"print(\"GCD of 12 and 18 is:\", gcd(12, 18))\n",
"print(\"LCM of 12 and 18 is:\", lcm(12, 18))\n",
"\n",
"\n",
"assistant: {\n",
" \"thought\": \"I should run the python file to see if the changes I made have fixed the gcd function.\",\n",
" \"action\": {\n",
" \"name\": \"execute_shell_command\",\n",
" \"arguments\": {\n",
" \"command\": \"python3 gcd.py\"\n",
" }\n",
" }\n",
"}\n",
"assistant: \n",
"====Observation====\n",
"GCD of 12 and 18 is: 6\n",
"LCM of 12 and 18 is: 36\n",
"assistant: {\n",
" \"thought\": \"The gcd.py file has been fixed and tested successfully. Therefore, no further actions are needed.\",\n",
" \"action\": {\n",
" \"name\": \"exit\",\n",
" \"arguments\": {}\n",
" }\n",
"}\n",
"assistant: \n",
"====Observation====\n",
"Current task finished, exitting.\n"
]
}
],
"source": [
"from loguru import logger\n",
"from agentscope.message import Msg\n",
"\n",
"mss = Msg(\n",
" name=\"user\", \n",
" content=\"The file in gcd.py is not returning the correct answer, can you fix the file for me?\", \n",
" role=\"user\"\n",
")\n",
"logger.chat(mss)\n",
"answer_mss = agent(mss)"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Conlusion\n",
"\n",
"After a few iterations, the SWE-agent assistant finish the job successfully, and the code is now working fine."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Above we shown a example of how to use the SWE-agent to fix code errors.\n",
"Although the design of the SWE-agent is primarily aimed at addressing GitHub issues, with modifications, it can also be utilized for more general programming tasks.\n",
"\n",
"Currently, how to enable agent with general programming ablities remains a challenging open question, with the efficacy of agent programming potentially influenced by factors such as prompt construction, model capabilities, and the complexity of the task at hand. Here we just provide an interesting toy example. \n",
"\n",
"We encourage users to experiment by altering the prompts within this example or by assigning different tasks to the agent, among other methods of exploration. Please feel free to experiment and explore on your own. The AgentScope team will continue to provide updates, enhancing the capabilities of the Programming Agents in the future!"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "datajuicer",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.18"
}
},
"nbformat": 4,
"nbformat_minor": 2
}

View File

@@ -0,0 +1,274 @@
# -*- coding: utf-8 -*-
"""An agent class that partially implements the SWE-agent.
SWE-agent is an agent designed for solving github issues.
More details can be found in https://swe-agent.com/.
Here we partially implement and modified the SWE-agent,
try to make it work with wider range of tasks then just fixing github issues.
"""
from agentscope.agents import AgentBase
from agentscope.message import Msg
from agentscope.exception import ResponseParsingError
from agentscope.parsers import MarkdownJsonDictParser
from typing import List, Callable, Optional, Union, Sequence
import json
from agentscope.service import (
ServiceFactory,
execute_shell_command,
)
from swe_agent_service_func import (
exec_py_linting,
write_file,
read_file,
)
from swe_agent_prompts import (
get_system_prompt,
get_context_prompt,
get_step_prompt,
)
def prepare_func_prompt(function: Callable) -> str:
func, desc = ServiceFactory.get(function)
func_name = desc["function"]["name"]
func_desc = desc["function"]["description"]
args_desc = desc["function"]["parameters"]["properties"]
args_list = [f"{func_name}: {func_desc}"]
for args_name, args_info in args_desc.items():
if "type" in args_info:
args_line = (
f'\t{args_name} ({args_info["type"]}): '
f'{args_info.get("description", "")}'
)
else:
args_line = f'\t{args_name}: {args_info.get("description", "")}'
args_list.append(args_line)
func_prompt = "\n".join(args_list)
return func_prompt
COMMANDS_DISCRIPTION_DICT = {
"exit": "exit: Executed when the current task is complete, takes no arguments", # noqa
"scroll_up": "scroll_up: Scrolls up the current open file, will scroll up and show you the 100 lines above your current lines, takes no arguments", # noqa
"scroll_down": "scroll_down: Scrolls down the current open file, will scroll down and show you the 100 lines below your current lines'takes no arguments", # noqa
"goto": "goto: This will take you directly to the line <line_num> and show you the 100 lines below it. \n line_num (int): The line number to go to.", # noqa
}
COMMANDS_DISCRIPTION_DICT["write_file"] = prepare_func_prompt(write_file)
COMMANDS_DISCRIPTION_DICT["read_file"] = prepare_func_prompt(read_file)
COMMANDS_DISCRIPTION_DICT["execute_shell_command"] = prepare_func_prompt(
execute_shell_command,
)
COMMANDS_DISCRIPTION_DICT["exec_py_linting"] = prepare_func_prompt(
exec_py_linting,
)
ERROR_INFO_PROMPT = """Your response is not a JSON object, and cannot be parsed by `json.loads` in parse function:
## Your Response:
[YOUR RESPONSE BEGIN]
{response}
[YOUR RESPONSE END]
## Error Information:
{error_info}
Analyze the reason, and re-correct your response in the correct format.""" # pylint: disable=all # noqa
def count_file_lines(file_path: str) -> int:
with open(file_path, "r") as file:
lines = file.readlines()
return len(lines)
class SWEAgent(AgentBase):
"""
The SWE-agent
"""
def __init__(
self,
name: str,
model_config_name: str,
) -> None:
""" """
super().__init__(
name=name,
model_config_name=model_config_name,
)
self.memory_window = 6
self.max_retries = 2
self.running_memory: List[str] = []
self.cur_file: str = ""
self.cur_line: int = 0
self.cur_file_content: str = ""
self.main_goal = ""
self.commands_prompt = ""
self.parser = MarkdownJsonDictParser()
self.get_commands_prompt()
def get_current_file_content(self) -> None:
"""
Get the current file content.
"""
if self.cur_file == "":
return
start_line = self.cur_line - 50
if start_line < 0:
start_line = 0
end_line = self.cur_line + 50
if end_line > count_file_lines(self.cur_file):
end_line = -1
read_res = read_file(self.cur_file, start_line, end_line)
self.cur_file_content = read_res.content
def step(self) -> Msg:
"""
Step the SWE-agent.
"""
message_list = []
# construct system prompt
system_prompt = get_system_prompt(self.commands_prompt)
message_list.append(Msg("user", system_prompt, role="system"))
# construct context prompt, i.e. previous actions
context_prompt = get_context_prompt(
self.running_memory,
self.memory_window,
)
message_list.append(Msg("user", context_prompt, role="user"))
# construct step prompt for this instance
self.get_current_file_content()
step_prompt = get_step_prompt(
self.main_goal,
self.cur_file,
self.cur_line,
self.cur_file_content,
)
message_list.append(Msg("user", step_prompt, role="user"))
# get response from agent
try:
in_prompt = self.model.format(message_list)
res = self.model(
in_prompt,
parse_func=self.parser.parse,
max_retries=1,
)
except ResponseParsingError as e:
response_msg = Msg(self.name, e.raw_response, "assistant")
self.speak(response_msg)
# Re-correct by model itself
error_msg = Msg(
name="system",
content={
"action": {"name": "error"},
"error_msg": ERROR_INFO_PROMPT.format(
parse_func=self.parser.parse,
error_info=e.message,
response=e.raw_response,
),
},
role="system",
)
self.speak(error_msg)
# continue
self.running_memory.append(error_msg)
return error_msg
msg_res = Msg(self.name, res.parsed, role="assistant")
self.speak(
Msg(self.name, json.dumps(res.parsed, indent=4), role="assistant"),
)
# parse and execute action
action = res.parsed.get("action")
obs = self.prase_command(res.parsed["action"])
self.speak(
Msg(self.name, "\n====Observation====\n" + obs, role="assistant"),
)
# add msg to context windows
self.running_memory.append(str(action) + str(obs))
return msg_res
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
action_name = None
self.main_goal = x.content
while not action_name == "exit":
msg = self.step()
action_name = msg.content["action"]["name"]
return msg
def prase_command(self, command_call: dict) -> str:
command_name = command_call["name"]
command_args = command_call["arguments"]
if command_name == "exit":
return "Current task finished, exitting."
if command_name in ["goto", "scroll_up", "scroll_down"]:
if command_name == "goto":
line = command_call["arguments"]["line_num"]
command_str = f"Going to {self.cur_file} line \
{command_args['line_mum']}."
command_failed_str = f"Failed to go to {self.cur_file} \
line {command_args['line_num']}"
if command_name == "scroll_up":
line = self.cur_line - 100
if line < 0:
line = 0
command_str = (
f"Scrolling up from file {self.cur_file} to line {line}."
)
command_failed_str = (
f"Failed to scroll up {self.cur_file} to line {line}"
)
if command_name == "scroll_down":
line = self.cur_line + 100
if line > count_file_lines(self.cur_file):
line = count_file_lines(self.cur_file)
command_str = (
f"Scrolling down from file {self.cur_file} to line {line}."
)
command_failed_str = (
f"Failed to scrool down {self.cur_file} to line {line}"
)
read_status = read_file(self.cur_file, line, line + 100)
if read_status.status == "success":
self.cur_line = line
obs = read_status.content
return f"{command_str}. Observe file content: {obs}"
else:
return command_failed_str
if command_name == "execute_shell_command":
return execute_shell_command(**command_args).content
if command_name == "write_file":
self.cur_file = command_args["file_path"]
self.cur_line = command_args.get("start_line", 0)
write_status = write_file(**command_args)
return write_status.content
if command_name == "read_file":
self.cur_file = command_args["file_path"]
self.cur_line = command_args.get("start_line", 0)
read_status = read_file(**command_args)
return read_status.content
if command_name == "exec_py_linting":
return exec_py_linting(**command_args).content
return "No such command"
def get_commands_prompt(self) -> None:
for name, desc in COMMANDS_DISCRIPTION_DICT.items():
self.commands_prompt += f"{name}: {desc}\n"

View File

@@ -0,0 +1,123 @@
# -*- coding: utf-8 -*-
# pylint: disable=C0301
"""The SWE-agent relay heavily on it's prompts.
This file contains the neccessary prompts for the SWE-agent.
Some prompts are taken and modified from the original SWE-agent repo
or the SWE-agent implementation from Open-Devin.
"""
WINDOW = 100
def get_system_prompt(command_prompt: str) -> str:
"""
Get the system prompt for SWE-agent.
"""
return f"""
SETTING:
You are an autonomous coding agent, here to perform codding tasks given the instruction.
You have been designed with a wide range of programming tasks, from code editing and debugging to testing and deployment.
You have access to a variety of tools and commands that you can use to help you solve problems efficiently.
You're working directly in the command line with a special interface.
The special interface consists of a file editor that shows you {WINDOW} lines of a file at a time.
In addition to typical bash commands, you can also use the following commands to help you navigate and edit files.
COMMANDS:
{command_prompt}
Please note that THE WRITE COMMAND REQUIRES PROPER INDENTATION.
If you'd like to add the line ' print(x)' you must fully write that out, with all those spaces before the code!
Indentation is important and code that is not indented correctly will fail and require fixing before it can be run.
If you'd like to issue two commands at once, PLEASE DO NOT DO THAT! Please instead first submit just the first command, and then after receiving a response you'll be able to issue the second command.
You're free to use any other bash commands you want (e.g. find, grep, cat, ls) in addition to the special commands listed above.
However, the environment does NOT support interactive session commands (e.g. vim, python), so please do not invoke them.
{RESPONSE_FORMAT_PROMPT}
""" # noqa
RESPONSE_FORMAT_PROMPT = """
## Response Format:
You should respond with a JSON object in the following format.
```json
{
"thought": "what you thought",
"action": {"name": "{command name}", "arguments": {"{argument1 name}": xxx, "{argument2 name}": xxx}}
}
```
For Example:
```json
{
"thought": "First I'll start by using ls to see what files are in the current directory. Then maybe we can look at some relevant files to see what they look like.",
"action": {"name": "execute_shell_command", "arguments": {"command": "ls -a"}}
}
```
OUTPUT the JSON format and ONLY OUTPUT the JSON format.
Your Response should always be a valid JSON string that can be parsed.
""" # noqa
def get_step_prompt(
task: str,
file: str,
line: int,
current_file_content: str,
) -> str:
"""
Get the step prompt for SWE-agent.
"""
return f"""
We're currently perform the following coding task. Here's the original task description from the user.
{task}
CURRENT
Open File: {file} on line {line}
Current File Content:
{current_file_content}
You can use these commands with the current file:
Navigation: `scroll_up`, `scroll_down`, and `goto <line>`
INSTRUCTIONS:
1. If you run a command and it doesn't work, try running a different command. A command that did not work once will not work the second time unless you modify it!
2. If you open a file and need to get to an area around a specific line that is not in the first 100 lines, say line 583, don't just use the scroll_down command multiple times. Instead, use the goto 583 command. It's much quicker.
3. Always make sure to look at the currently open file and the current working directory (which appears right after the currently open file). The currently open file might be in a different directory! Note that some commands, such as 'write_file' and 'read_file', open files, so they might change the current open file.
4. When editing files, it is easy to accidentally specify a wrong line number or to write code with incorrect indentation. Always check the code after you issue an edit to make sure that it reflects what you wanted to accomplish. If it didn't, issue another command to fix it.
5. After modifying python files, you can run `exec_py_linting` to check for errors. If there are errors, fix them and repeat the previous step.
NOTE THAT THIS ENVIRONMENT DOES NOT SUPPORT INTERACTIVE SESSION COMMANDS, such as "vim" or "python", or "python3". So DONOT execute them by running `execute_shell_command` with `python` command or `python3` command if the code need additional inputs.
If you want to check whether a python file is valid, you can use `exec_py_linting` to check for errors.
You should always notice your response format and respond with a JSON object in the following format.
{RESPONSE_FORMAT_PROMPT}
""" # noqa
def get_context_prompt(memory: list, window: int) -> str:
"""
Get the context prompt for the given memory and window.
"""
res = f"These are your past {window} actions:\n"
window_size = window if len(memory) > window else len(memory)
cur_mems = memory[-window_size:]
res += "===== Previous Actions =====\n"
for idx, mem in enumerate(cur_mems):
res += f"\nMemory {idx}:\n{mem}\n"
res += "======= End Actions =======\n"
res += "Use these memories to provide additional context to \
the problem you are solving.\nRemember that you have already \
completed these steps so you do not need to perform them again."
return res

View File

@@ -0,0 +1,147 @@
# -*- coding: utf-8 -*-
# pylint: disable=C0301
"""
Tools for swe-agent, such as checking files with linting and formatting,
writing and reading files by lines, etc.
"""
import subprocess
import os
from agentscope.service.service_response import ServiceResponse
from agentscope.service.service_status import ServiceExecStatus
def exec_py_linting(file_path: str) -> ServiceResponse:
"""
Executes flake8 linting on the given .py file with specified checks and
returns the linting result.
Args:
file_path (`str`): The path to the Python file to lint.
Returns:
ServiceResponse: Contains either the output from the flake8 command as
a string if successful, or an error message including the error type.
"""
command = f"flake8 --isolated --select=F821,F822,F831,\
E111,E112,E113,E999,E902 {file_path}"
try:
result = subprocess.run(
command,
shell=True,
check=True,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
return ServiceResponse(
status=ServiceExecStatus.SUCCESS,
content=result.stdout.strip()
if result.stdout
else "No lint errors found.",
)
except subprocess.CalledProcessError as e:
error_message = (
e.stderr.strip()
if e.stderr
else "An error occurred while linting the file."
)
return ServiceResponse(
status=ServiceExecStatus.ERROR,
content=error_message,
)
except Exception as e:
return ServiceResponse(
status=ServiceExecStatus.ERROR,
content=str(e),
)
def write_file(
file_path: str,
content: str,
start_line: int = 0,
end_line: int = -1,
) -> ServiceResponse:
"""
Write content to a file by replacing the current lines between <start_line> and <end_line> with <content>. Default start_line = 0 and end_line = -1. Calling this with no <start_line> <end_line> args will replace the whole file, so besure to use this with caution when writing to a file that already exists.
Args:
file_path (`str`): The path to the file to write to.
content (`str`): The content to write to the file.
start_line (`Optional[int]`, defaults to `0`): The start line of the file to be replace with <content>.
end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be replace with <content>. end_line = -1 means the end of the file, otherwise it should be a positive integer indicating the line number.
""" # noqa
try:
mode = "w" if not os.path.exists(file_path) else "r+"
insert = content.split("\n")
with open(file_path, mode, encoding="utf-8") as file:
if mode != "w":
all_lines = file.readlines()
new_file = [""] if start_line == 0 else all_lines[:start_line]
new_file += [i + "\n" for i in insert]
last_line = end_line + 1
new_file += [""] if end_line == -1 else all_lines[last_line:]
else:
new_file = insert
file.seek(0)
file.writelines(new_file)
file.truncate()
obs = f'WRITE OPERATION:\nYou have written to "{file_path}" \
on these lines: {start_line}:{end_line}.'
return ServiceResponse(
status=ServiceExecStatus.SUCCESS,
content=obs + "".join(new_file),
)
except Exception as e:
error_message = f"{e.__class__.__name__}: {e}"
return ServiceResponse(
status=ServiceExecStatus.ERROR,
content=error_message,
)
def read_file(
file_path: str,
start_line: int = 0,
end_line: int = -1,
) -> ServiceResponse:
"""
Shows a given file's contents starting from <start_line> up to <end_line>. Default: start_line = 0, end_line = -1. By default the whole file will be read.
Args:
file_path (`str`): The path to the file to read.
start_line (`Optional[int]`, defaults to `0`): The start line of the file to be read.
end_line (`Optional[int]`, defaults to `-1`): The end line of the file to be read.
""" # noqa
start_line = max(start_line, 0)
try:
with open(file_path, "r", encoding="utf-8") as file:
if end_line == -1:
if start_line == 0:
code_view = file.read()
else:
all_lines = file.readlines()
code_slice = all_lines[start_line:]
code_view = "".join(code_slice)
else:
all_lines = file.readlines()
num_lines = len(all_lines)
begin = max(0, min(start_line, num_lines - 2))
end_line = (
-1 if end_line > num_lines else max(begin + 1, end_line)
)
code_slice = all_lines[begin:end_line]
code_view = "".join(code_slice)
return ServiceResponse(
status=ServiceExecStatus.SUCCESS,
content=f"{code_view}",
)
except Exception as e:
error_message = f"{e.__class__.__name__}: {e}"
return ServiceResponse(
status=ServiceExecStatus.ERROR,
content=error_message,
)

View File

@@ -0,0 +1,41 @@
# Distributed Conversation
This example will show
- How to set up and run a distributed conversation.
- How to configure and use different language models in the system.
## Background
This example demonstrates a distributed dialog system leveraging various language models. The system is designed to handle conversational AI tasks in a distributed manner, allowing for scalable and efficient dialog management.
## Tested Models
These models are tested in this example. For other models, some modifications may be needed.
- Ollama Chat (llama3_8b)
- Dashscope Chat (qwen-Max)
- Gemini Chat (gemini-pro)
## Prerequisites
Before running the example, please install the distributed version of Agentscope, fill in your model configuration correctly in `configs/model_configs.json`, and modify the `model_config_name` field in `distributed_dialog.py` accordingly.
## Running the Example
Use the following command to start the assistant agent:
```
cd examples/distributed_basic
python distributed_dialog.py --role assistant --assistant-host localhost --assistant-port 12010
# Please make sure the port is available.
# If the assistant agent and the user agent are started on different machines,
# please fill in the ip address of the assistant agent in the host field
```
Then, run the user agent:
```
python distributed_dialog.py --role user --assistant-host localhost --assistant-port 12010
# If the assistant agent is started on another machine,
# please fill in the ip address of the assistant agent in the host field
```
Now, you can chat with the assistant agent using the command line.

View File

@@ -0,0 +1,21 @@
[
{
"config_name": "gpt-4",
"model_type": "openai_chat",
"model_name": "gpt-4",
"api_key": "xxx",
"organization": "xxx",
"generate_args": {
"temperature": 0.5
}
},
{
"config_name": "qwen",
"model_type": "dashscope_chat",
"model_name": "qwen-max",
"api_key": "xxx",
"generate_args": {
"temperature": 0.5
}
}
]

View File

@@ -0,0 +1,83 @@
# -*- coding: utf-8 -*-
""" An example of distributed dialog """
import argparse
from loguru import logger
import agentscope
from agentscope.agents.user_agent import UserAgent
from agentscope.agents.dialog_agent import DialogAgent
from agentscope.server import RpcAgentServerLauncher
def parse_args() -> argparse.Namespace:
"""Parse arguments"""
parser = argparse.ArgumentParser()
parser.add_argument(
"--role",
choices=["assistant", "user"],
default="user",
)
parser.add_argument(
"--assistant-port",
type=int,
default=12010,
)
parser.add_argument(
"--assistant-host",
type=str,
default="localhost",
)
return parser.parse_args()
def setup_assistant_server(assistant_host: str, assistant_port: int) -> None:
"""Set up assistant rpc server"""
agentscope.init(
model_configs="configs/model_configs.json",
project="Distributed Conversation",
)
assistant_server_launcher = RpcAgentServerLauncher(
host=assistant_host,
port=assistant_port,
)
assistant_server_launcher.launch()
assistant_server_launcher.wait_until_terminate()
def run_main_process(assistant_host: str, assistant_port: int) -> None:
"""Run dialog main process"""
agentscope.init(
model_configs="configs/model_configs.json",
project="Distributed Conversation",
)
assistant_agent = DialogAgent(
name="Assistant",
sys_prompt="You are a helpful assistant.",
model_config_name="qwen",
use_memory=True,
).to_dist(
host=assistant_host,
port=assistant_port,
)
user_agent = UserAgent(
name="User",
require_url=False,
)
logger.info(
"Setup successfully, have fun chatting! (enter 'exit' to close the "
"agent)",
)
msg = user_agent()
while not msg.content.endswith("exit"):
msg = assistant_agent(msg)
logger.chat(msg)
msg = user_agent(msg)
if __name__ == "__main__":
args = parse_args()
if args.role == "assistant":
setup_assistant_server(args.assistant_host, args.assistant_port)
elif args.role == "user":
run_main_process(args.assistant_host, args.assistant_port)

View File

@@ -0,0 +1,80 @@
# Distributed Debate Competition
This example demonstrates:
- How to simulate a debate competition with three participant agents
- How to allow human participation in the debate
## Background
This example simulates a debate competition with three participant agents:
1. The affirmative side (**Pro**)
2. The negative side (**Con**)
3. The adjudicator (**Judge**)
The debate topic is whether AGI can be achieved using the GPT model framework. Pro argues in favor, while Con contests it. Judge listens to both sides' arguments and provides an analytical judgment on which side presented a more compelling case.
Each agent is an independent process and can run on different machines. Human participants can join as Pro or Con.
## Tested Models
These models are tested in this example. For other models, some modifications may be needed.
- Ollama Chat (qwen2:1.5b)
- Dashscope Chat (qwen-Max)
- Gemini Chat (gemini-pro)
## Prerequisites
Before running the example:
- Install the distributed version of AgentScope by running
```bash
# On windows
pip install -e .[distribute]
# On mac / linux
pip install -e .\[distribute\]
```
- Fill in your model configuration correctly in `configs/model_configs.json`
- Modify the `model_config_name` field in `configs/debate_agent_configs.json` accordingly
- Ensure the specified ports are available and IP addresses are accessible
## Setup and Execution
### Step 1: Setup Pro and Con agent servers
For an LLM-based Pro:
```shell
cd examples/distributed_debate
python distributed_debate.py --role pro --pro-host localhost --pro-port 12011
```
(Alternatively) for human participation as Pro:
```shell
python distributed_debate.py --role pro --pro-host localhost --pro-port 12011 --is-human
```
For an LLM-based Con:
```shell
python distributed_debate.py --role con --con-host localhost --con-port 12012
```
(Alternatively) for human participation as Con:
```shell
python distributed_debate.py --role con --con-host localhost --con-port 12012 --is-human
```
### Step 2: Run the main process
```shell
python distributed_debate.py --role main --pro-host localhost --pro-port 12011 --con-host localhost --con-port 12012
```
### Step 3: Watch or join the debate in your terminal
If you join as Con, you'll see something like:
```text
System: Welcome to the debate on whether Artificial General Intelligence (AGI) can be achieved
...
Pro: Thank you. I argue that AGI can be achieved using the GPT model framework.
...
User Input:
```

View File

@@ -0,0 +1,29 @@
[
{
"class": "DialogAgent",
"args": {
"name": "Pro",
"sys_prompt": "Assume the role of a debater who is arguing in favor of the proposition that AGI (Artificial General Intelligence) can be achieved using the GPT model framework. Construct a coherent and persuasive argument, including scientific, technological, and theoretical evidence, to support the statement that GPT models are a viable path to AGI. Highlight the advancements in language understanding, adaptability, and scalability of GPT models as key factors in progressing towards AGI.",
"model_config_name": "gpt-3.5-turbo",
"use_memory": true
}
},
{
"class": "DialogAgent",
"args": {
"name": "Con",
"sys_prompt": "Assume the role of a debater who is arguing against the proposition that AGI can be achieved using the GPT model framework. Construct a coherent and persuasive argument, including scientific, technological, and theoretical evidence, to support the statement that GPT models, while impressive, are insufficient for reaching AGI. Discuss the limitations of GPT models such as lack of understanding, consciousness, ethical reasoning, and general problem-solving abilities that are essential for true AGI.",
"model_config_name": "gpt-3.5-turbo",
"use_memory": true
}
},
{
"class": "DialogAgent",
"args": {
"name": "Judge",
"sys_prompt": "Assume the role of an impartial judge in a debate where the affirmative side argues that AGI can be achieved using the GPT model framework, and the negative side contests this. Listen to both sides' arguments and provide an analytical judgment on which side presented a more compelling and reasonable case. Consider the strength of the evidence, the persuasiveness of the reasoning, and the overall coherence of the arguments presented by each side.",
"model_config_name": "gpt-3.5-turbo",
"use_memory": true
}
}
]

View File

@@ -0,0 +1,21 @@
[
{
"config_name": "gpt-4",
"model_type": "openai_chat",
"model_name": "gpt-4",
"api_key": "xxx",
"organization": "xxx",
"generate_args": {
"temperature": 0.5
}
},
{
"config_name": "qwen",
"model_type": "dashscope_chat",
"model_name": "qwen-max",
"api_key": "xxx",
"generate_args": {
"temperature": 0.5
}
}
]

View File

@@ -0,0 +1,129 @@
# -*- coding: utf-8 -*-
""" An example of distributed debate """
import argparse
from user_proxy_agent import UserProxyAgent
from loguru import logger
import agentscope
from agentscope.agents import DialogAgent
from agentscope.msghub import msghub
from agentscope.server import RpcAgentServerLauncher
from agentscope.message import Msg
FIRST_ROUND = """
Welcome to the debate on whether Artificial General Intelligence (AGI) can be achieved using the GPT model framework. This debate will consist of three rounds. In each round, the affirmative side will present their argument first, followed by the negative side. After both sides have presented, the adjudicator will summarize the key points and analyze the strengths of the arguments.
The rules are as follows:
Each side must present clear, concise arguments backed by evidence and logical reasoning.
No side may interrupt the other while they are presenting their case.
After both sides have presented, the adjudicator will have time to deliberate and will then provide a summary, highlighting the most persuasive points from both sides.
The adjudicator's summary will not declare a winner for the individual rounds but will focus on the quality and persuasiveness of the arguments.
At the conclusion of the three rounds, the adjudicator will declare the overall winner based on which side won two out of the three rounds, considering the consistency and strength of the arguments throughout the debate.
Let us begin the first round. The affirmative side: please present your argument for why AGI can be achieved using the GPT model framework.
""" # noqa
SECOND_ROUND = """
Let us begin the second round. It's your turn, the affirmative side.
"""
THIRD_ROUND = """
Next is the final round.
"""
END = """
Judge, please declare the overall winner now.
"""
def parse_args() -> argparse.Namespace:
"""Parse arguments"""
parser = argparse.ArgumentParser()
parser.add_argument(
"--role",
choices=["pro", "con", "main"],
default="main",
)
parser.add_argument("--is-human", action="store_true")
parser.add_argument("--pro-host", type=str, default="localhost")
parser.add_argument(
"--pro-port",
type=int,
default=12011,
)
parser.add_argument("--con-host", type=str, default="localhost")
parser.add_argument(
"--con-port",
type=int,
default=12012,
)
parser.add_argument("--judge-host", type=str, default="localhost")
parser.add_argument(
"--judge-port",
type=int,
default=12013,
)
return parser.parse_args()
def setup_server(parsed_args: argparse.Namespace) -> None:
"""Setup rpc server for participant agent"""
agentscope.init(
model_configs="configs/model_configs.json",
project="Distributed Conversation",
)
host = getattr(parsed_args, f"{parsed_args.role}_host")
port = getattr(parsed_args, f"{parsed_args.role}_port")
server_launcher = RpcAgentServerLauncher(
host=host,
port=port,
custom_agents=[UserProxyAgent, DialogAgent],
)
server_launcher.launch(in_subprocess=False)
server_launcher.wait_until_terminate()
def run_main_process(parsed_args: argparse.Namespace) -> None:
"""Setup the main debate competition process"""
pro_agent, con_agent, judge_agent = agentscope.init(
model_configs="configs/model_configs.json",
agent_configs="configs/debate_agent_configs.json",
project="Distributed Conversation",
)
pro_agent = pro_agent.to_dist(
host=parsed_args.pro_host,
port=parsed_args.pro_port,
)
con_agent = con_agent.to_dist(
host=parsed_args.con_host,
port=parsed_args.con_port,
)
participants = [pro_agent, con_agent, judge_agent]
announcements = [
Msg(name="system", content=FIRST_ROUND, role="system"),
Msg(name="system", content=SECOND_ROUND, role="system"),
Msg(name="system", content=THIRD_ROUND, role="system"),
]
end = Msg(name="system", content=END, role="system")
with msghub(participants=participants) as hub:
for i in range(3):
hub.broadcast(announcements[i])
pro_resp = pro_agent()
logger.chat(pro_resp)
con_resp = con_agent()
logger.chat(con_resp)
judge_agent()
hub.broadcast(end)
judge_agent()
if __name__ == "__main__":
args = parse_args()
if args.role == "main":
run_main_process(args)
else:
setup_server(args)

View File

@@ -0,0 +1,31 @@
# -*- coding: utf-8 -*-
"""User Proxy Agent class for distributed usage"""
from typing import Sequence, Union
from typing import Optional
from agentscope.agents import UserAgent
from agentscope.message import Msg
class UserProxyAgent(UserAgent):
"""User proxy agent class"""
def reply( # type: ignore[override]
self,
x: Optional[Union[Msg, Sequence[Msg]]] = None,
required_keys: Optional[Union[list[str], str]] = None,
) -> Msg:
"""
Reply with `self.speak(x)`
"""
if x is not None:
self.speak(x)
return super().reply(x, required_keys)
def observe(self, x: Union[dict, Sequence[dict]]) -> None:
"""
Observe with `self.speak(x)`
"""
if x is not None:
self.speak(x) # type: ignore[arg-type]
self.memory.add(x)

View File

@@ -0,0 +1,83 @@
# Multi-Agent Copilot Search
## Introduction
This example application converts the user's questions into keywords to call the search engine, and then retrieves a series of web pages to find answers. It involves three types of Agents, namely the UserAgent for the user, the SearcherAgent responsible for searching, and the AnswererAgent responsible for retrieves web pages.
There are many web page links returned by the search engine. To improve performance, multiple instances of AnswererAgent need to run together. However, with the traditional single-process mode, even if there are multiple AnswererAgent instances, they can only obtain web page and answer questions one by one on a single CPU.
But, with AgentScope's distributed mode, you can automatically make these AnswererAgent instances run at the same time to improve performance.
From this example, you can learn:
- how to run multiple agents in different processes,
- how to make multiple agents run in parallel automatically,
- how to convert a single-process version AgentScope application into a multi-processes version.
## How to Run
### Step 0: Install AgentScope distributed version
This example requires the distributed version of AgentScope.
```bash
# On windows
pip install -e .[distribute]
# On mac / linux
pip install -e .\[distribute\]
```
### Step 1: Prepare your model and search engine API configuration
For the model configuration, please fill your model configurations in `configs/model_configs.json`.
Here we give an example.
> Dashscope models, e.g. qwen-max, and openai models, e.g. gpt-3.5-turbo and gpt-4 are tested for this example.
> Other models may require certain modification to the code.
```json
[
{
"config_name": "my_model",
"model_type": "dashscope_chat",
"model_name": "qwen-max",
"api_key": "your_api_key",
"generate_args": {
"temperature": 0.5
},
"messages_key": "input"
}
]
```
For search engines, this example now supports two types of search engines, google and bing. The configuration items for each of them are as follows:
- google
- `api-key`
- `cse-id`
- bing
- `api-key`
### Step 2: Run the example
Use the `main.py` script to run the example. The following are the parameters required to run the script:
- `--num-workers`: The number of AnswererAgent instances.
- `--use-dist`: Enable distributed mode.
- `--search-engine`: The search engine used, currently supports `google` or `bing`.
- `--api-key`: API key for google or bing.
- `--cse-id`: CSE id for google (If you use bing, ignore this parameter).
For example, if you want to start the example application in distributed mode with 10 AnswererAgents and use the bing search engine, you can use the following command
```shell
python main.py --num-workers 10 --search-engine bing --api-key xxxxx --use-dist
```
And if you want to run the above case in a traditional single-process mode, you can use the following command.
```shell
python main.py --num-workers 10 --search-engine bing --api-key xxxxx
```
You can ask the same question in both modes to compare the difference in runtime. For examples, answer a question with 10 workers only takes 13.2s in distributed mode, while it takes 51.3s in single-process mode.

View File

@@ -0,0 +1,61 @@
# -*- coding: utf-8 -*-
"""Answerer Agent."""
from typing import Optional, Union, Sequence
from agentscope.message import Msg
from agentscope.agents import AgentBase
from agentscope.service import load_web
class AnswererAgent(AgentBase):
"""An agent with web digest tool."""
def __init__(
self,
name: str,
model_config_name: str = None,
) -> None:
super().__init__(
name=name,
sys_prompt="You are an AI assistant. You need to find answers to "
"user questions based on specified web content.",
model_config_name=model_config_name,
use_memory=False,
)
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
response = load_web(
url=x.url,
keep_raw=False,
html_selected_tags=["p", "div", "h1", "li"],
timeout=5,
).content
if (
"html_to_text" not in response
or len(response["html_to_text"]) == 0
):
return Msg(
self.name,
content=f"Unable to load web page [{x.url}].",
url=x.url,
)
# prepare prompt
prompt = self.model.format(
Msg(name="system", role="system", content=self.sys_prompt),
Msg(
name="user",
role="user",
content=f"Please answer my question based on the content of"
" the following web page:\n\n"
f"{response['html_to_text']}"
f"\n\nBased on the above web page,"
f" please answer my question\n{x.query}",
),
)
# call llm and generate response
response = self.model(prompt).text
msg = Msg(self.name, content=response, url=x.url)
self.speak(msg)
return msg

View File

@@ -0,0 +1,12 @@
[
{
"model_type": "tongyi_chat",
"config_name": "my_model",
"model_name": "qwen-max",
"api_key": "your_api_key",
"generate_args": {
"temperature": 0.5
},
"messages_key": "input"
}
]

View File

@@ -0,0 +1,78 @@
# -*- coding: utf-8 -*-
"""An example use multiple agents to search the Internet for answers"""
import time
import argparse
from loguru import logger
from searcher_agent import SearcherAgent
from answerer_agent import AnswererAgent
import agentscope
from agentscope.agents.user_agent import UserAgent
from agentscope.message import Msg
def parse_args() -> argparse.Namespace:
"""Parse arguments"""
parser = argparse.ArgumentParser()
parser.add_argument("--num-workers", type=int, default=5)
parser.add_argument("--use-dist", action="store_true")
parser.add_argument(
"--search-engine",
type=str,
choices=["google", "bing"],
default="google",
)
parser.add_argument(
"--api-key",
type=str,
)
parser.add_argument("--cse-id", type=str, default=None)
return parser.parse_args()
if __name__ == "__main__":
args = parse_args()
agentscope.init(
model_configs="configs/model_configs.json",
project="Parallel Optimization",
)
WORKER_NUM = 3
searcher = SearcherAgent(
name="Searcher",
model_config_name="my_model",
result_num=args.num_workers,
search_engine_type=args.search_engine,
api_key=args.api_key,
cse_id=args.cse_id,
)
answerers = []
for i in range(args.num_workers):
answerer = AnswererAgent(
name=f"Answerer-{i}",
model_config_name="my_model",
)
if args.use_dist:
answerer = answerer.to_dist(lazy_launch=False)
answerers.append(answerer)
user_agent = UserAgent()
msg = user_agent()
while not msg.content == "exit":
start_time = time.time()
msg = searcher(msg)
results = []
for page, worker in zip(msg.content, answerers):
results.append(worker(Msg(**page)))
for result in results:
logger.chat(result)
end_time = time.time()
logger.chat(
Msg(
name="system",
role="system",
content=f"Completed in [{end_time - start_time:.2f}]s",
),
)
msg = user_agent()

View File

@@ -0,0 +1,97 @@
# -*- coding: utf-8 -*-
"""Searcher agent."""
from functools import partial
from typing import Optional, Union, Sequence
from agentscope.message import Msg
from agentscope.agents import AgentBase
from agentscope.service import google_search, bing_search
class SearcherAgent(AgentBase):
"""An agent with search tool."""
def __init__(
self,
name: str,
model_config_name: str = None,
result_num: int = 10,
search_engine_type: str = "google",
api_key: str = None,
cse_id: str = None,
) -> None:
"""Init a SearcherAgent.
Args:
name (`str`): the name of this agent.
model_config_name (`str`, optional): The name of model
configuration for this agent. Defaults to None.
result_num (`int`, optional): The number of return results.
Defaults to 10.
search_engine_type (`str`, optional): the search engine to use.
Defaults to "google".
api_key (`str`, optional): api key for the search engine. Defaults
to None.
cse_id (`str`, optional): cse_id for the search engine. Defaults to
None.
"""
super().__init__(
name=name,
sys_prompt="You are an AI assistant who optimizes search"
" keywords. You need to transform users' questions into a series "
"of efficient search keywords.",
model_config_name=model_config_name,
use_memory=False,
)
self.result_num = result_num
if search_engine_type == "google":
assert (api_key is not None) and (
cse_id is not None
), "google search requires 'api_key' and 'cse_id'"
self.search = partial(
google_search,
api_key=api_key,
cse_id=cse_id,
)
elif search_engine_type == "bing":
assert api_key is not None, "bing search requires 'api_key'"
self.search = partial(bing_search, api_key=api_key)
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
prompt = self.model.format(
Msg(name="system", role="system", content=self.sys_prompt),
x,
Msg(
name="user",
role="user",
content="Please convert the question into keywords. The return"
" format is:\nKeyword1 Keyword2...",
),
)
query = self.model(prompt).text
results = self.search(
question=query,
num_results=self.result_num,
).content
msg = Msg(
self.name,
content=[
Msg(
name=self.name,
content=result,
url=result["link"],
query=x.content,
)
for result in results
],
)
self.speak(
Msg(
name=self.name,
role="assistant",
content="Search results:\n"
f"{[result['link'] for result in results]}",
),
)
return msg

View File

@@ -0,0 +1,103 @@
# Distributed Large Scale Simulation
> **WARNING:**
> **This example will consume a huge amount of tokens.**
> **Using paid model API with this example can introduce a high cost.**
> **Users with powerful GPUs (A800 or better) can use local inference services (such as vLLM) to run this example,**
> **while CPU inference services such as ollama is not recommended.**
This example is a large scale simulation to demonstrate the scalability of AgentScope's distributed mode. From this example, you can learn:
- How to run a large number of agent servers in a GPU cluster.
- How to connect to those agent servers and run a huge number of agents in them.
> Based on this example, we deploy 64,000 agents evenly on 4 machines, and each machine has 64 CPU cores and 8 A100 GPUs. The running time is about 30s (excluding initialization time).
## Background
This example simulates the following scenario:
A large number of people participate in a game in which the moderator asks each participant to provide a number between 0 and N. The moderator will calculate the average of all numbers and announce it. The person closest to the average will win.
## Tested Models
Only vLLM local inference service is tested for this example.
This example will consume a huge amount of tokens. Please do not use model API that requires payment.
## Prerequisites
- The distribute version of AgentScope is installed
- Use MacOS or Linux (Windows requires some modifiations to the scripts)
- [Optional] Have multiple machines with powerful GPUs (A800 or better) and install [vLLM](https://github.com/vllm-project/vllm)
## How to Run
### Step 1: start local inference service
> If you only have one machine and don't have a powerful GPU (A800 or better), you can ignore this step.
You can use `start_vllm.sh` to start vllm inference services on each of your machines.
Before running the script, please set `gpu_num`, `model_path` and `base_port` properly.
- `gpu_num`: number of GPUs for this machine.
- `model_path`: the model checkpoint path.
- `base_port`: The starting point of the port number used by the local inference services.
For example, if `base_port` is `8010` and `gpu_num` is `4`, 4 inference services will be started, and the port numbers are `8010`, `8011`, `8012` and `8013` respectively.
vLLM inference services start slowly, so you need to wait for these servers to actually start before proceeding to the next step.
> The above configuration requires that the model checkpoint can be loaded by a single GPU.
> If you need to use a model that must be loaded by multiple GPUs, you need to modify the script.
### Step 2: start agent server
> If you only have one machine and don't have a powerful GPU, you can just use the default setting of the scripts.
You can use `start_all_server.sh` to start multiple agent servers on each of your machine.
Before running the script, please set `base_port`, `host_name` and `moderator_num` properly.
- `base_port`: The starting point of the port number used by the agent servers.
- `host_name`: The hostname of this machine, and must be accessible to other machines in the cluster (The default value `localhost` is only used for single machine scenario).
- `moderator_num`: Number of moderators. When the number of participants is large, this value needs to be expanded to avoid bottlenecks.
After setting the above values correctly, you can use the script to start multiple agent server on your machine. The following command will start 10 agent servers on your machine with port numbers starting from `base_port` to `base_port + 9`, and will also start `moderator_num` agent servers for moderators with port numbers starting from `base_port + 10` to `base_port + moderator_num + 9`.
```shell
#./start_all_server.sh <number_of_server_per_host>
./start_all_server.sh 10
```
If you have multiple machines, please make sure the `base_port` and `moderator_num` parameters are exactly the same on all machines, and start the same number of agent servers.
### Step 3: run simulation
You can use `run_simulation.sh` to start the simulation.
Before running the script, please set the following setting correctly:
- `base_port`: the base port for agent servers, must be the same as used in Step 2.
- `hosts`: hostnames of all machines. If you only have one machine, use the default value `localhost`.
- `moderator_per_host`: Consistent with `moderator_num` in Step 2.
- `agent_type`: `random` or `llm`. Please use `random` if you don't have local inference service.
- `max_value`: The upper bound of numbers generated in the game.
The command below will run a simulation with 1000 participant agents and evenly distributed those agents to the 10 agent servers started in Step 2.
```shell
#./run_simulation.sh <number_of_server_per_host> <total_number_of_participant>
./run_simulation.sh 10 1000
```
The following is sample output from a single-machine (16 CPU cores) simulation scenario:
```log
2024-04-16 10:31:53.786 | INFO | agentscope.models:read_model_configs:178 - Load configs for model wrapper: model_1, model_2, model_3, model_4, model_5, model_6, model_7, model_8
2024-04-16 10:31:53.822 | INFO | agentscope.utils.monitor:_create_monitor_table:343 - Init [monitor_metrics] as the monitor table
2024-04-16 10:31:53.822 | INFO | agentscope.utils.monitor:_create_monitor_table:344 - Init [monitor_metrics_quota_exceeded] as the monitor trigger
2024-04-16 10:31:53.822 | INFO | agentscope.utils.monitor:__init__:313 - SqliteMonitor initialization completed at [./runs/run_20240416-103153_h0xuo5/agentscope.db]
2024-04-16 10:31:53.829 | INFO | __main__:run_main_process_new:106 - init 1000 random participant agents...
2024-04-16 10:31:53.829 | INFO | __main__:run_main_process_new:139 - init 4 moderator agents...
2024-04-16 10:31:54.211 | INFO | __main__:run_main_process_new:163 - [init takes 0.38274645805358887 s]
Moderator: The average value is 49.561 [takes 4.197571277618408 s]
```

View File

@@ -0,0 +1,98 @@
[
{
"model_type": "openai_chat",
"config_name": "model_1",
"model_name": "path-to-your-model-dir",
"api_key": "EMPTY",
"client_args": {
"base_url": "http://127.0.0.1:8010/v1/"
},
"generate_args": {
"temperature": 1.0
}
},
{
"model_type": "openai_chat",
"config_name": "model_2",
"model_name": "path-to-your-model-dir",
"api_key": "EMPTY",
"client_args": {
"base_url": "http://127.0.0.1:8011/v1/"
},
"generate_args": {
"temperature": 1.0
}
},
{
"model_type": "openai_chat",
"config_name": "model_3",
"model_name": "path-to-your-model-dir",
"api_key": "EMPTY",
"client_args": {
"base_url": "http://127.0.0.1:8012/v1/"
},
"generate_args": {
"temperature": 1.0
}
},
{
"model_type": "openai_chat",
"config_name": "model_4",
"model_name": "path-to-your-model-dir",
"api_key": "EMPTY",
"client_args": {
"base_url": "http://127.0.0.1:8013/v1/"
},
"generate_args": {
"temperature": 1.0
}
},
{
"model_type": "openai_chat",
"config_name": "model_5",
"model_name": "path-to-your-model-dir",
"api_key": "EMPTY",
"client_args": {
"base_url": "http://127.0.0.1:8014/v1/"
},
"generate_args": {
"temperature": 1.0
}
},
{
"model_type": "openai_chat",
"config_name": "model_6",
"model_name": "path-to-your-model-dir",
"api_key": "EMPTY",
"client_args": {
"base_url": "http://127.0.0.1:8015/v1/"
},
"generate_args": {
"temperature": 1.0
}
},
{
"model_type": "openai_chat",
"config_name": "model_7",
"model_name": "path-to-your-model-dir",
"api_key": "EMPTY",
"client_args": {
"base_url": "http://127.0.0.1:8016/v1/"
},
"generate_args": {
"temperature": 1.0
}
},
{
"model_type": "openai_chat",
"config_name": "model_8",
"model_name": "path-to-your-model-dir",
"api_key": "EMPTY",
"client_args": {
"base_url": "http://127.0.0.1:8017/v1/"
},
"generate_args": {
"temperature": 1.0
}
}
]

View File

@@ -0,0 +1,216 @@
# -*- coding: utf-8 -*-
""" A large-scale social simulation experiment """
import argparse
import time
from concurrent import futures
from concurrent.futures import as_completed
from loguru import logger
from participant import Moderator, RandomParticipant, LLMParticipant
import agentscope
from agentscope.agents import AgentBase
from agentscope.server import RpcAgentServerLauncher
from agentscope.message import Msg
def parse_args() -> argparse.Namespace:
"""Parse arguments"""
parser = argparse.ArgumentParser()
parser.add_argument(
"--role",
choices=["participant", "main"],
default="main",
)
parser.add_argument(
"--agent-type",
choices=["random", "llm"],
default="random",
)
parser.add_argument("--max-value", type=int, default=100)
parser.add_argument("--sleep-time", type=float, default=1.0)
parser.add_argument(
"--hosts",
type=str,
nargs="+",
default=["localhost"],
)
parser.add_argument("--participant-num", type=int, default=100)
parser.add_argument("--base-port", type=int, default=12010)
parser.add_argument(
"--server-per-host",
type=int,
)
parser.add_argument("--model-per-host", type=int, default=1)
parser.add_argument("--moderator-per-host", type=int, default=1)
return parser.parse_args()
def setup_participant_agent_server(host: str, port: int) -> None:
"""Set up agent server"""
agentscope.init(
project="simulation",
name="server",
runtime_id=str(port),
save_code=False,
save_api_invoke=False,
model_configs="configs/model_configs.json",
use_monitor=False,
)
assistant_server_launcher = RpcAgentServerLauncher(
host=host,
port=port,
max_pool_size=16384,
custom_agents=[Moderator, RandomParticipant, LLMParticipant],
)
assistant_server_launcher.launch(in_subprocess=False)
assistant_server_launcher.wait_until_terminate()
def init_moderator(
name: str,
configs: list[dict],
host: str,
port: int,
agent_type: str,
max_value: int,
sleep_time: float,
) -> AgentBase:
"""Init moderator"""
return Moderator( # pylint: disable=E1123
name=name,
part_configs=configs,
agent_type=agent_type,
max_value=max_value,
sleep_time=sleep_time,
to_dist={
"host": host,
"port": port,
},
)
def run_main_process(
hosts: list[str],
base_port: int,
server_per_host: int,
model_per_host: int,
participant_num: int,
moderator_per_host: int = 10,
agent_type: str = "random",
max_value: int = 100,
sleep_time: float = 1.0,
) -> None:
"""Run main process"""
agentscope.init(
project="simulation",
name="main",
save_code=False,
save_api_invoke=False,
model_configs="configs/model_configs.json",
use_monitor=False,
)
host_num = len(hosts)
total_agent_server_num = server_per_host * host_num
participant_per_agent_server = participant_num // total_agent_server_num
ist = time.time()
configs = []
logger.info(f"init {participant_num} {agent_type} participant agents...")
# build init configs of participants
for i in range(participant_num):
idx = i // participant_per_agent_server
host_id = idx // server_per_host
port_id = idx % server_per_host
model_id = i % model_per_host
host = hosts[host_id]
port = base_port + port_id
config_name = f"model_{model_id + 1}"
if agent_type == "random":
configs.append(
{
"name": f"P{i}",
"host": host,
"port": port,
},
)
else:
configs.append(
{
"name": f"P{i}",
"model_config_name": config_name,
"host": host,
"port": port,
},
)
mods = []
moderator_num = moderator_per_host * host_num
participant_per_moderator = participant_num // moderator_num
tasks = []
logger.info(f"init {moderator_num} moderator agents...")
# init moderators
with futures.ThreadPoolExecutor(max_workers=None) as executor:
for i in range(moderator_num):
tasks.append(
executor.submit(
init_moderator,
name=f"mod_{i}",
configs=configs[
i
* participant_per_moderator : (i + 1) # noqa
* participant_per_moderator
],
host=hosts[i // moderator_per_host],
port=base_port + server_per_host + i % moderator_per_host,
agent_type=agent_type,
max_value=max_value,
sleep_time=sleep_time,
),
)
for task in as_completed(tasks):
mods.append(task.result())
iet = time.time()
logger.info(f"[init takes {iet - ist} s]")
# run te
st = time.time()
results = []
for p in mods:
results.append(p())
summ = 0
cnt = 0
for r in results:
try:
summ += int(r["content"]["sum"])
cnt += int(r["content"]["cnt"])
except Exception:
logger.error(r["content"])
et = time.time()
logger.chat(
Msg(
name="Moderator",
role="assistant",
content=f"The average value is {summ/cnt} [takes {et-st} s]",
),
)
if __name__ == "__main__":
args = parse_args()
if args.role == "participant":
setup_participant_agent_server(args.hosts[0], args.base_port)
elif args.role == "main":
run_main_process(
hosts=args.hosts,
base_port=args.base_port,
participant_num=args.participant_num,
server_per_host=args.server_per_host,
model_per_host=args.model_per_host,
moderator_per_host=args.moderator_per_host,
agent_type=args.agent_type,
sleep_time=args.sleep_time,
max_value=args.max_value,
)

View File

@@ -0,0 +1,158 @@
# -*- coding: utf-8 -*-
"""A general dialog agent."""
import random
import time
import re
from typing import Optional, Union, Sequence
from loguru import logger
from agentscope.message import Msg
from agentscope.agents import AgentBase
class RandomParticipant(AgentBase):
"""A fake participant who generates number randomly."""
def __init__(
self,
name: str,
max_value: int = 100,
sleep_time: float = 1.0,
) -> None:
"""Initialize the participant."""
super().__init__(
name=name,
)
self.max_value = max_value
self.sleep_time = sleep_time
def generate_random_response(self) -> str:
"""generate a random int"""
time.sleep(self.sleep_time)
return str(random.randint(0, self.max_value))
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
"""Generate a random value"""
# generate a response in content
response = self.generate_random_response()
msg = Msg(self.name, content=response)
return msg
class LLMParticipant(AgentBase):
"""A participant agent who generates number using LLM."""
def __init__(
self,
name: str,
model_config_name: str,
max_value: int = 100,
) -> None:
"""Initialize the participant."""
super().__init__(
name=name,
model_config_name=model_config_name,
use_memory=True,
)
self.max_value = max_value
self.prompt = Msg(
name="system",
role="system",
content="You are participating in a game where everyone "
f"provides a number between 0 and {max_value}. The person "
"closest to the average will win.",
)
def parse_value(self, txt: str) -> str:
"""Parse the number from the response."""
numbers = re.findall(r"\d+", txt)
if len(numbers) == 0:
logger.warning(
f"Fail to parse value from [{txt}], use "
f"{self.max_value // 2} instead.",
)
return str(self.max_value // 2)
else:
return numbers[-1]
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
"""Generate a value by LLM"""
if self.memory:
self.memory.add(x)
# prepare prompt
prompt = self.model.format(self.prompt, self.memory.get_memory())
# call llm and generate response
response = self.model(prompt).text
response = self.parse_value(response)
msg = Msg(self.name, response, role="assistant")
# Record the message in memory
if self.memory:
self.memory.add(msg)
return msg
class Moderator(AgentBase):
"""A Moderator to collect values from participants."""
def __init__(
self,
name: str,
part_configs: list[dict],
agent_type: str = "random",
max_value: int = 100,
sleep_time: float = 1.0,
) -> None:
super().__init__(name)
self.max_value = max_value
if agent_type == "llm":
self.participants = [
LLMParticipant(
name=config["name"],
model_config_name=config["model_config_name"],
max_value=max_value,
).to_dist(
host=config["host"],
port=config["port"],
)
for config in part_configs
]
else:
self.participants = [
RandomParticipant(
name=config["name"],
max_value=max_value,
sleep_time=sleep_time,
).to_dist(
host=config["host"],
port=config["port"],
)
for config in part_configs
]
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
results = []
msg = Msg(
name="moderator",
role="user",
content=f"Now give a number between 0 and {self.max_value}.",
)
for p in self.participants:
results.append(p(msg))
summ = 0
for r in results:
try:
summ += int(r["content"])
except Exception as e:
print(e)
return Msg(
name=self.name,
role="assistant",
content={"sum": summ, "cnt": len(self.participants)},
)

View File

@@ -0,0 +1,25 @@
#!/bin/bash
# default values
base_port=12330
hosts="localhost" # or "server1 server2 server3 ..."
moderator_per_host=4
model_per_host=8
agent_type="random" # or "llm"
max_value=100
# check server-per-host
if ! [[ "$1" =~ ^[0-9]+$ ]]; then
echo "Usage: $0 <server-per-host> <participant-num>"
exit 1
fi
# check participant-num
if ! [[ "$2" =~ ^[0-9]+$ ]]; then
echo "Usage: $0 <server-per-host> <participant-num>"
exit 1
fi
mkdir -p log
python main.py --role main --hosts ${hosts} --base-port ${base_port} --participant-num $2 --server-per-host $1 --model-per-host ${model_per_host} --moderator-per-host ${moderator_per_host} --agent-type ${agent_type} --max-value ${max_value}

View File

@@ -0,0 +1,29 @@
#!/bin/bash
# default values
base_port=12330
host_name="localhost"
moderator_num=4
# get number of server
if ! [[ "$1" =~ ^[0-9]+$ ]]; then
echo "Usage: $0 <number-of-server-for-participant>"
exit 1
fi
participant_server_num=$1
# create files for pid
> .pid
# create log dir
mkdir -p log
# start all agent servers
for ((i=0; i<(participant_server_num + moderator_num); i++)); do
port=$((base_port + i))
python main.py --role participant --hosts ${host_name} --base-port ${port} > log/${port}.log 2>&1 &
echo $! >> .pid
echo "Started agent server on ${host_name}:${port} with PID $!"
done
echo "All servers started"

View File

@@ -0,0 +1,19 @@
#!/bin/bash
# default values
gpu_num=8
model_path="path-to-your-model-dir"
base_port=8010
> .vllm_pid
mkdir -p log
for ((i=0; i<8; i++)); do
port=$((base_port + i))
export CUDA_VISIBLE_DEVICES=$i
python -m vllm.entrypoints.openai.api_server --model "${model_path}" --port ${port} --enforce-eager > log/vllm-${port}.log 2>&1 &
echo $! >> .vllm_pid
echo "Started vllm server on port ${port} with PID $!"
done
echo "All vllm server started"

View File

@@ -0,0 +1,19 @@
#!/bin/bash
if [ ! -f .pid ]; then
echo "PID file not found. Are the servers running?"
exit 1
fi
while read pid; do
kill -9 $pid
if [ $? -eq 0 ]; then
echo "Killed server with PID $pid"
else
echo "Failed to kill server with PID $pid"
fi
done < .pid
rm .pid
echo "All servers stopped."

View File

@@ -0,0 +1,19 @@
#!/bin/bash
if [ ! -f .vllm_pid ]; then
echo "PID file not found. Are the servers running?"
exit 1
fi
while read pid; do
kill -9 $pid
if [ $? -eq 0 ]; then
echo "Killed vllm server with PID $pid"
else
echo "Failed to kill vllm server with PID $pid"
fi
done < .vllm_pid
rm .vllm_pid
echo "All vllm servers stopped."

View File

@@ -0,0 +1,173 @@
# -*- coding: utf-8 -*-
"""A board agent class that can host a Gomoku game, and a function to
convert the board to an image."""
from typing import Optional, Union, Sequence
import numpy as np
from matplotlib import pyplot as plt, patches
from agentscope.message import Msg
from agentscope.agents import AgentBase
CURRENT_BOARD_PROMPT_TEMPLATE = """The current board is as follows:
{board}
{player}, it's your turn."""
NAME_BLACK = "Alice"
NAME_WHITE = "Bob"
# The mapping from name to piece
NAME_TO_PIECE = {
NAME_BLACK: "o",
NAME_WHITE: "x",
}
EMPTY_PIECE = "0"
def board2img(board: np.ndarray, save_path: str) -> str:
"""Convert the board to an image and save it to the specified path."""
size = board.shape[0]
fig, ax = plt.subplots(figsize=(10, 10))
ax.set_xlim(0, size - 1)
ax.set_ylim(0, size - 1)
for i in range(size):
ax.axhline(i, color="black", linewidth=1)
ax.axvline(i, color="black", linewidth=1)
for y in range(size):
for x in range(size):
if board[y, x] == NAME_TO_PIECE[NAME_BLACK]: # black player
circle = patches.Circle(
(x, y),
0.45,
edgecolor="black",
facecolor="black",
zorder=10,
)
ax.add_patch(circle)
elif board[y, x] == NAME_TO_PIECE[NAME_WHITE]: # white player
circle = patches.Circle(
(x, y),
0.45,
edgecolor="black",
facecolor="white",
zorder=10,
)
ax.add_patch(circle)
# Hide the axes and invert the y-axis
ax.set_xticks(range(size))
ax.set_yticks(range(size))
ax.set_xticklabels(range(size))
ax.set_yticklabels(range(size))
ax.invert_yaxis()
plt.savefig(save_path, bbox_inches="tight", pad_inches=0.1)
plt.close(fig) # Close the figure to free memory
return save_path
class BoardAgent(AgentBase):
"""A board agent that can host a Gomoku game."""
def __init__(self, name: str) -> None:
super().__init__(name=name, use_memory=False)
# Init the board
self.size = 15
self.board = np.full((self.size, self.size), EMPTY_PIECE)
# Record the status of the game
self.game_end = False
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
if x is None:
# Beginning of the game
content = (
"Welcome to the Gomoku game! Black player goes first. "
"Please make your move."
)
else:
row, col = x.content
self.assert_valid_move(row, col)
# change the board
self.board[row, col] = NAME_TO_PIECE[x.name]
# check if the game ends
if self.check_draw():
content = "The game ends in a draw!"
self.game_end = True
else:
next_player_name = (
NAME_BLACK if x.name == NAME_WHITE else NAME_WHITE
)
content = CURRENT_BOARD_PROMPT_TEMPLATE.format(
board=self.board2text(),
player=next_player_name,
)
if self.check_win(row, col, NAME_TO_PIECE[x.name]):
content = f"The game ends, {x.name} wins!"
self.game_end = True
msg_host = Msg(self.name, content, role="assistant")
self.speak(msg_host)
# Note: we disable the image display here to avoid too many images
# img = plt.imread(board2img(self.board, 'current_board.png'))
# plt.imshow(img)
# plt.axis('off')
# plt.show()
return msg_host
def assert_valid_move(self, x: int, y: int) -> None:
"""Check if the move is valid."""
if not (0 <= x < self.size and 0 <= y < self.size):
raise RuntimeError(f"Invalid move: {[x, y]} out of board range.")
if not self.board[x, y] == EMPTY_PIECE:
raise RuntimeError(
f"Invalid move: {[x, y]} is already "
f"occupied by {self.board[x, y]}.",
)
def check_win(self, x: int, y: int, piece: str) -> bool:
"""Check if the player wins the game."""
xline = self._check_line(self.board[x, :], piece)
yline = self._check_line(self.board[:, y], piece)
diag1 = self._check_line(np.diag(self.board, y - x), piece)
diag2 = self._check_line(
np.diag(np.fliplr(self.board), self.size - 1 - x - y),
piece,
)
return xline or yline or diag1 or diag2
def check_draw(self) -> bool:
"""Check if the game ends in a draw."""
return np.all(self.board != EMPTY_PIECE)
def board2text(self) -> str:
"""Convert the board to a text representation."""
return "\n".join(
[
str(_)[1:-1].replace("'", "").replace(" ", "")
for _ in self.board
],
)
def _check_line(self, line: np.ndarray, piece: str) -> bool:
"""Check if the player wins in a line."""
count = 0
for i in line:
if i == piece:
count += 1
if count == 5:
return True
else:
count = 0
return False

View File

@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
"""The main script to start a Gomoku game between two agents and a board
agent."""
from board_agent import NAME_TO_PIECE, NAME_BLACK, NAME_WHITE, BoardAgent
from gomoku_agent import GomokuAgent
from agentscope import msghub
import agentscope
MAX_STEPS = 10
SYS_PROMPT_TEMPLATE = """
You're a skillful Gomoku player. You should play against your opponent according to the following rules:
Game Rules:
1. This Gomoku board is a 15*15 grid. Moves are made by specifying row and column indexes, with [0, 0] marking the top-left corner and [14, 14] indicating the bottom-right corner.
2. The goal is to be the first player to form an unbroken line of your pieces horizontally, vertically, or diagonally.
3. If the board is completely filled with pieces and no player has formed a row of five, the game is declared a draw.
Note:
1. Your pieces are represented by '{}', your opponent's by '{}'. 0 represents an empty spot on the board.
2. You should think carefully about your strategy and moves, considering both your and your opponent's subsequent moves.
3. Make sure you don't place your piece on a spot that has already been occupied.
4. Only an unbroken line of five same pieces will win the game. For example, "xxxoxx" won't be considered a win.
5. Note the unbroken line can be formed in any direction: horizontal, vertical, or diagonal.
""" # noqa
# Prepare the model configuration
YOUR_MODEL_CONFIGURATION_NAME = "{YOUR_MODEL_CONFIGURATION_NAME}"
YOUR_MODEL_CONFIGURATION = {
"config_name": YOUR_MODEL_CONFIGURATION_NAME,
# ...
}
# Initialize the agents
agentscope.init(model_configs=YOUR_MODEL_CONFIGURATION)
piece_black = NAME_TO_PIECE[NAME_BLACK]
piece_white = NAME_TO_PIECE[NAME_WHITE]
black = GomokuAgent(
NAME_BLACK,
model_config_name=YOUR_MODEL_CONFIGURATION_NAME,
sys_prompt=SYS_PROMPT_TEMPLATE.format(piece_black, piece_white),
)
white = GomokuAgent(
NAME_WHITE,
model_config_name=YOUR_MODEL_CONFIGURATION_NAME,
sys_prompt=SYS_PROMPT_TEMPLATE.format(piece_white, piece_black),
)
board = BoardAgent(name="Host")
# Start the game!
msg = None
i = 0
# Use a msg hub to share conversation between two players, e.g. white player
# can hear what black player says to the board
with msghub(participants=[black, white, board]):
while not board.game_end and i < MAX_STEPS:
for player in [black, white]:
# receive the move from the player, judge if the game ends and
# remind the player to make a move
msg = board(msg)
# end the game if draw or win
if board.game_end:
break
# make a move
msg = player(msg)
i += 1

View File

@@ -0,0 +1,82 @@
# -*- coding: utf-8 -*-
"""A Gomoku agent that can play the game with another agent."""
from typing import Optional, Union, Sequence
import json
from agentscope.message import Msg
from agentscope.agents import AgentBase
from agentscope.models import ModelResponse
HINT_PROMPT = """
You should respond in the following format, which can be loaded by json.loads in Python:
{{
"thought": "analyze the present situation, and what move you should make",
"move": [row index, column index]
}}
""" # noqa
def parse_func(response: ModelResponse) -> ModelResponse:
"""Parse the response from the model into a dict with "move" and "thought"
keys."""
res_dict = json.loads(response.text)
if "move" in res_dict and "thought" in res_dict:
return ModelResponse(raw=res_dict)
else:
raise ValueError(
f"Invalid response format in parse_func "
f"with response: {response.text}",
)
class GomokuAgent(AgentBase):
"""A Gomoku agent that can play the game with another agent."""
def __init__(
self,
name: str,
sys_prompt: str,
model_config_name: str,
) -> None:
super().__init__(
name=name,
sys_prompt=sys_prompt,
model_config_name=model_config_name,
)
self.memory.add(Msg("system", sys_prompt, role="system"))
def reply(self, x: Optional[Union[Msg, Sequence[Msg]]] = None) -> Msg:
if self.memory:
self.memory.add(x)
msg_hint = Msg("system", HINT_PROMPT, role="system")
prompt = self.model.format(
self.memory.get_memory(),
msg_hint,
)
response = self.model(
prompt,
parse_func=parse_func,
max_retries=3,
).raw
# For better presentation, we print the response proceeded by
# json.dumps, this msg won't be recorded in memory
self.speak(
Msg(
self.name,
json.dumps(response, indent=4, ensure_ascii=False),
role="assistant",
),
)
if self.memory:
self.memory.add(Msg(self.name, response, role="assistant"))
# Hide thought from the response
return Msg(self.name, response["move"], role="assistant")

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,81 @@
# Werewolf Game in AgentScope
This example demonstrates how to use AgentScope to play the Werewolf game, where six agents play against each other as werewolves and villagers.
You will learn the following features in AgentScope:
- How to make an agent respond with **different specified fields** in **different situations** in AgentScope (Like a finite state machine!)
- How to use **msghub** and **pipeline** in AgentScope to construct a game with **COMPLEX SOP**
## Background
The werewolf game involves a complex SOP with multiple roles and different phases. In these phases, players with different roles should take different actions, e.g. discussion, voting, checking roles (seer), using potions (witch), and so on.
Therefore, for an agent in werewolf game, it should be able to switch its status according to the game phase and its role, and respond accordingly.
Of course, we can hard code the SOP (or finite state machine) within the agent, but we expect the agent to be more **flexible**, **intelligent**, and **adaptive**, which means **the agent shouldn't be designed for a specific game only**. It should be able to adapt to different tasks and SOPs!
To achieve this goal, in AgentScope, we use a built-in [DictDialogAgent](https://github.com/modelscope/agentscope/blob/main/src/agentscope/agents/dict_dialog_agent.py) class, together with a [parser](https://modelscope.github.io/agentscope/en/tutorial/203-parser.html) module to construct the werewolf game.
Moreover, the [pipeline and msghub](https://modelscope.github.io/agentscope/en/tutorial/202-pipeline.html) in AgentScope enable us to easily construct a complex SOP with multiple agents. We hope the implementation is concise, clear and readable!
More details please refer to our tutorial:
- [Parser](https://modelscope.github.io/agentscope/en/tutorial/203-parser.html)
- [Pipeline and msghub](https://modelscope.github.io/agentscope/en/tutorial/202-pipeline.html)
https://github.com/DavdGao/AgentScope/assets/102287034/86951418-e1cc-486b-a3dc-b237a0108994
## Tested Models
This example has been tested with the following models:
- dashscope_chat (qwen-turbo)
- ollama_chat (llama3_8b)
## Prerequisites
To run this example, you need to:
- Set your API key in `./configs/model_configs.json`
[Optional] To play the game in person, see [werewolf.py](werewolf.py) for the complete code.
## Code Snippets
### About Pipeline and MsgHub
The following code is the implementation of daytime discussion in Werewolf. With msghub and pipeline, it's very easy to program a discussion among agents.
```python
# ...
with msghub(survivors, announcement=hints) as hub:
# discuss
set_parsers(survivors, Prompts.survivors_discuss_parser)
x = sequentialpipeline(survivors)
# ...
```
### About Parser
The parser is used to specify the required fields in the agent's response and how to handle them. Here's an example of a parser configuration:
```python
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",
)
```
In this example, the `MarkdownJsonDictParser` is used to parse the agent's response. The `content_hint` parameter specifies the expected fields and their descriptions. The `required_keys` parameter indicates the mandatory fields in the response. The `keys_to_memory` and `keys_to_content` parameters determine which fields should be stored in memory and included in the content of the returned message, respectively.

View File

@@ -0,0 +1,56 @@
[
{
"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 is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win 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 response only based on the conversation history and your strategy.\n\nYou are playing werewolf in this game.\n",
"model_config_name": "gpt-4",
"use_memory": true
}
},
{
"class": "DictDialogAgent",
"args": {
"name": "Player2",
"sys_prompt": "Act as a player in a werewolf game. You are Player2 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 is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win 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 response only based on the conversation history and your strategy.\n\nYou are playing werewolf in this game.\n",
"model_config_name": "gpt-4",
"use_memory": true
}
},
{
"class": "DictDialogAgent",
"args": {
"name": "Player3",
"sys_prompt": "Act as a player in a werewolf game. You are Player3 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 is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win 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 response only based on the conversation history and your strategy.\n\nYou are playing villager in this game.\n",
"model_config_name": "gpt-4",
"use_memory": true
}
},
{
"class": "DictDialogAgent",
"args": {
"name": "Player4",
"sys_prompt": "Act as a player in a werewolf game. You are Player4 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 is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win 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 response only based on the conversation history and your strategy.\n\nYou are playing villager in this game.\n",
"model_config_name": "gpt-4",
"use_memory": true
}
},
{
"class": "DictDialogAgent",
"args": {
"name": "Player5",
"sys_prompt": "Act as a player in a werewolf game. You are Player5 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 is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win 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 response only based on the conversation history and your strategy.\n\nYou are playing seer in this game.\n",
"model_config_name": "gpt-4",
"use_memory": true
}
},
{
"class": "DictDialogAgent",
"args": {
"name": "Player6",
"sys_prompt": "Act as a player in a werewolf game. You are Player6 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 is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win 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 response only based on the conversation history and your strategy.\n\nYou are playing witch in this game.\n",
"model_config_name": "gpt-4",
"use_memory": true
}
}
]

View File

@@ -0,0 +1,19 @@
[
{
"model_type": "openai_chat",
"config_name": "gpt-4",
"model_name": "gpt-4",
"api_key": "xxx",
"organization": "xxx",
"generate_args": {
"temperature": 0.5
}
},
{
"model_type": "post_api_chat",
"config_name": "my_post_api",
"api_url": "https://xxx",
"headers": {},
"json_args": {}
}
]

View File

@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
"""Used to record prompts, will be replaced by configuration"""
from agentscope.parsers.json_object_parser import MarkdownJsonDictParser
class Prompts:
"""Prompts for werewolf game"""
to_wolves = (
"{}, if you are the only werewolf, eliminate a player. Otherwise, "
"discuss with your teammates and reach an agreement."
)
wolves_discuss_parser = MarkdownJsonDictParser(
content_hint={
"thought": "what you thought",
"speak": "what you speak",
"finish_discussion": "whether the discussion reached an "
"agreement or not (true/false)",
},
required_keys=["thought", "speak", "finish_discussion"],
keys_to_memory="speak",
keys_to_content="speak",
keys_to_metadata=["finish_discussion"],
)
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",
)
to_wolves_res = "The player with the most votes is {}."
to_witch_resurrect = (
"{witch_name}, you're the witch. Tonight {dead_name} is eliminated. "
"Would you like to resurrect {dead_name}?"
)
to_witch_resurrect_no = "The witch has chosen not to resurrect the player."
to_witch_resurrect_yes = "The witch has chosen to resurrect the player."
witch_resurrect_parser = MarkdownJsonDictParser(
content_hint={
"thought": "what you thought",
"speak": "whether to resurrect the player and the reason",
"resurrect": "whether to resurrect the player or not (true/false)",
},
required_keys=["thought", "speak", "resurrect"],
keys_to_memory="speak",
keys_to_content="speak",
keys_to_metadata=["resurrect"],
)
to_witch_poison = (
"Would you like to eliminate one player? If yes, "
"specify the player_name."
)
witch_poison_parser = MarkdownJsonDictParser(
content_hint={
"thought": "what you thought",
"speak": "what you speak",
"eliminate": "whether to eliminate a player or not (true/false)",
},
required_keys=["thought", "speak", "eliminate"],
keys_to_memory="speak",
keys_to_content="speak",
keys_to_metadata=["eliminate"],
)
to_seer = (
"{}, you're the seer. Which player in {} would you like to check "
"tonight?"
)
seer_parser = MarkdownJsonDictParser(
content_hint={
"thought": "what you thought",
"speak": "player_name",
},
required_keys=["thought", "speak"],
keys_to_memory="speak",
keys_to_content="speak",
)
to_seer_result = "Okay, the role of {} is a {}."
to_all_danger = (
"The day is coming, all the players open your eyes. Last night, "
"the following player(s) has been eliminated: {}."
)
to_all_peace = (
"The day is coming, all the players open your eyes. Last night is "
"peaceful, no player is eliminated."
)
to_all_discuss = (
"Now the alive players are {}. Given the game rules and your role, "
"based on the situation and the information you gain, to vote a "
"player eliminated among alive players and to win the game, what do "
"you want to say to others? You can decide whether to reveal your "
"role."
)
survivors_discuss_parser = MarkdownJsonDictParser(
content_hint={
"thought": "what you thought",
"speak": "what you speak",
},
required_keys=["thought", "speak"],
keys_to_memory="speak",
keys_to_content="speak",
)
survivors_vote_parser = MarkdownJsonDictParser(
content_hint={
"thought": "what you thought",
"vote": "player_name",
},
required_keys=["thought", "vote"],
keys_to_memory="vote",
keys_to_content="vote",
)
to_all_vote = (
"Given the game rules and your role, based on the situation and the"
" information you gain, to win the game, it's time to vote one player"
" eliminated among the alive players. Which player do you vote to "
"kill?"
)
to_all_res = "{} has been voted out."
to_all_wolf_win = (
"The werewolves have prevailed and taken over the village. Better "
"luck next time!"
)
to_all_village_win = (
"The game is over. The werewolves have been defeated, and the village "
"is safe once again!"
)
to_all_continue = "The game goes on."

View File

@@ -0,0 +1,149 @@
# -*- coding: utf-8 -*-
"""A werewolf game implemented by agentscope."""
from functools import partial
from prompt import Prompts
from werewolf_utils import (
check_winning,
update_alive_players,
majority_vote,
extract_name_and_id,
n2s,
set_parsers,
)
from agentscope.message import Msg
from agentscope.msghub import msghub
from agentscope.pipelines.functional import sequentialpipeline
import agentscope
# pylint: disable=too-many-statements
def main() -> None:
"""werewolf game"""
# default settings
HostMsg = partial(Msg, name="Moderator", role="assistant", echo=True)
healing, poison = True, True
MAX_WEREWOLF_DISCUSSION_ROUND = 3
MAX_GAME_ROUND = 6
# read model and agent configs, and initialize agents automatically
survivors = agentscope.init(
model_configs="./configs/model_configs.json",
agent_configs="./configs/agent_configs.json",
project="Werewolf",
)
roles = ["werewolf", "werewolf", "villager", "villager", "seer", "witch"]
wolves, witch, seer = survivors[:2], survivors[-1], survivors[-2]
# start the game
for _ 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
# 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])),
)
# witch
healing_used_tonight = False
if witch in survivors:
if healing:
hint = HostMsg(
content=Prompts.to_witch_resurrect.format_map(
{
"witch_name": witch.name,
"dead_name": dead_player[0],
},
),
)
set_parsers(witch, Prompts.witch_resurrect_parser)
if witch(hint).metadata.get("resurrect", False):
healing_used_tonight = True
dead_player.pop()
healing = False
HostMsg(content=Prompts.to_witch_resurrect_yes)
else:
HostMsg(content=Prompts.to_witch_resurrect_no)
if poison and not healing_used_tonight:
set_parsers(witch, Prompts.witch_poison_parser)
x = witch(HostMsg(content=Prompts.to_witch_poison))
if x.metadata.get("eliminate", False):
dead_player.append(extract_name_and_id(x.content)[0])
poison = False
# seer
if seer in survivors:
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)
survivors, wolves = update_alive_players(
survivors,
wolves,
dead_player,
)
if check_winning(survivors, wolves, "Moderator"):
break
# daytime discussion
content = (
Prompts.to_all_danger.format(n2s(dead_player))
if dead_player
else Prompts.to_all_peace
)
hints = [
HostMsg(content=content),
HostMsg(content=Prompts.to_all_discuss.format(n2s(survivors))),
]
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 result to all players
result = HostMsg(content=Prompts.to_all_res.format(vote_res))
hub.broadcast(result)
survivors, wolves = update_alive_players(
survivors,
wolves,
vote_res,
)
if check_winning(survivors, wolves, "Moderator"):
break
hub.broadcast(HostMsg(content=Prompts.to_all_continue))
if __name__ == "__main__":
main()

View File

@@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
"""utils."""
import re
from typing import Union, Any, Sequence
import numpy as np
from loguru import logger
from prompt import Prompts
from agentscope.agents import AgentBase
from agentscope.message import Msg
def check_winning(alive_agents: list, wolf_agents: list, host: str) -> bool:
"""check which group wins"""
if len(wolf_agents) * 2 >= len(alive_agents):
msg = Msg(host, Prompts.to_all_wolf_win, role="assistant")
logger.chat(msg)
return True
if alive_agents and not wolf_agents:
msg = Msg(host, Prompts.to_all_village_win, role="assistant")
logger.chat(msg)
return True
return False
def update_alive_players(
survivors: Sequence[AgentBase],
wolves: Sequence[AgentBase],
dead_names: Union[str, list[str]],
) -> tuple[list, list]:
"""update the list of alive agents"""
if not isinstance(dead_names, list):
dead_names = [dead_names]
return [_ for _ in survivors if _.name not in dead_names], [
_ for _ in wolves if _.name not in dead_names
]
def majority_vote(votes: list) -> Any:
"""majority_vote function"""
votes_valid = [item for item in votes if item != "Abstain"]
# Count the votes excluding abstentions.
unit, counts = np.unique(votes_valid, return_counts=True)
return unit[np.argmax(counts)]
def extract_name_and_id(name: str) -> tuple[str, int]:
"""extract player name and id from a string"""
try:
name = re.search(r"\b[Pp]layer\d+\b", name).group(0)
idx = int(re.search(r"[Pp]layer(\d+)", name).group(1)) - 1
except AttributeError:
# In case Player remains silent or speaks to abstain.
logger.warning(f"vote: invalid name {name}, set to Abstain")
name = "Abstain"
idx = -1
return name, idx
def n2s(agents: Sequence[Union[AgentBase, str]]) -> str:
"""combine agent names into a string, and use "and" to connect the last
two names."""
def _get_name(agent_: Union[AgentBase, str]) -> str:
return agent_.name if isinstance(agent_, AgentBase) else agent_
if len(agents) == 1:
return _get_name(agents[0])
return (
", ".join([_get_name(_) for _ in agents[:-1]])
+ " and "
+ _get_name(agents[-1])
)
def set_parsers(
agents: Union[AgentBase, list[AgentBase]],
parser_name: str,
) -> None:
"""Add parser to agents"""
if not isinstance(agents, list):
agents = [agents]
for agent in agents:
agent.set_parser(parser_name)

View File

@@ -0,0 +1,37 @@
[{
"config_name": "dashscope_chat-qwen-max",
"model_type": "dashscope_chat",
"model_name": "qwen-max",
"api_key": "{your_api_key}",
"generate_args": {
"temperature": 0.7
}
},
{
"config_name": "dashscope_chat-qwen-max-1201",
"model_type": "dashscope_chat",
"model_name": "qwen-max-1201",
"api_key": "{your_api_key}",
"generate_args": {
"temperature": 0.7
}
},
{
"config_name": "dashscope_chat-qwen-turbo",
"model_type": "dashscope_chat",
"model_name": "qwen-turbo",
"api_key": "{your_api_key}",
"generate_args": {
"temperature": 0.7
}
},
{
"config_name": "dashscope_chat-qwen-plus",
"model_type": "dashscope_chat",
"model_name": "qwen-plus",
"api_key": "{your_api_key}",
"generate_args": {
"temperature": 0.7
}
}
]

View File

@@ -0,0 +1,34 @@
[{
"config_name": "dashscope_image_synthesis-wanx-v1-1024x1024",
"model_type": "dashscope_image_synthesis",
"model_name": "wanx-v1",
"api_key": "{your_api_key}",
"generate_args": {
"n": 1,
"negative_prompt": "{unwanted_prompt}",
"size": "1024*1024"
}
},
{
"config_name": "dashscope_image_synthesis-wanx-v1-720x1280",
"model_type": "dashscope_image_synthesis",
"model_name": "wanx-v1",
"api_key": "{your_api_key}",
"generate_args": {
"n": 1,
"negative_prompt": "{unwanted_prompt}",
"size": "720*1280"
}
},
{
"config_name": "dashscope_image_synthesis-wanx-v1-1280x720",
"model_type": "dashscope_image_synthesis",
"model_name": "wanx-v1",
"api_key": "{your_api_key}",
"generate_args": {
"n": 1,
"negative_prompt": "{unwanted_prompt}",
"size": "1280*720"
}
}
]

View File

@@ -0,0 +1,40 @@
[{
"config_name": "dashscope_multimodal-qwen-vl-plus",
"model_type": "dashscope_multimodal",
"model_name": "qwen-vl-plus",
"api_key": "{your_api_key}",
"generate_args": {
}
},
{
"config_name": "dashscope_multimodal-qwen-vl-max",
"model_type": "dashscope_multimodal",
"model_name": "qwen-vl-max",
"api_key": "{your_api_key}",
"generate_args": {
}
},
{
"config_name": "dashscope_multimodal-qwen-audio-turbo",
"model_type": "dashscope_multimodal",
"model_name": "qwen-audio-turbo",
"api_key": "{your_api_key}",
"generate_args": {
}
},
{
"config_name": "dashscope_multimodal-qwen-vl-chat-v1",
"model_type": "dashscope_multimodal",
"model_name": "qwen-vl-chat-v1",
"api_key": "{your_api_key}",
"generate_args": {
}
},{
"config_name": "dashscope_multimodal-qwen-audio-chat-v1",
"model_type": "dashscope_multimodal",
"model_name": "qwen-vl-chat-v1",
"api_key": "{your_api_key}",
"generate_args": {
}
}
]

View File

@@ -0,0 +1,13 @@
[{
"config_name": "dashscope_text_embedding-text-embedding-v1",
"model_type": "dashscope_text_embedding",
"model_name": "text-embedding-v1",
"api_key": "{your_api_key}"
},
{
"config_name": "dashscope_text_embedding-text-embedding-v2",
"model_type": "dashscope_text_embedding",
"model_name": "text-embedding-v2",
"api_key": "{your_api_key}"
}
]

View File

@@ -0,0 +1,7 @@
[{
"config_name": "gemini_chat-gemini-pro",
"model_type": "gemini_chat",
"model_name": "gemini-pro",
"api_key": "{your_api_key}"
}
]

View File

@@ -0,0 +1,7 @@
[{
"config_name": "gemini_chat-embedding-001",
"model_type": "gemini_embedding",
"model_name": "models/embedding-001",
"api_key": "{your_api_key}"
}
]

View File

@@ -0,0 +1,11 @@
[{
"config_name": "lite_llm_openai_chat_gpt-3.5-turbo",
"model_type": "litellm_chat",
"model_name": "gpt-3.5-turbo"
},
{
"config_name": "lite_llm_claude3",
"model_type": "litellm_chat",
"model_name": "claude-3-opus-20240229"
}
]

View File

@@ -0,0 +1,37 @@
[{
"config_name": "ollama_chat-llama2",
"model_type": "ollama_chat",
"model_name": "llama2",
"options": {
"temperature": 0.7
},
"keep_alive": "5m"
},
{
"config_name": "ollama_chat-mistral",
"model_type": "ollama_chat",
"model_name": "mistral",
"options": {
"temperature": 0.7
},
"keep_alive": "5m"
},
{
"config_name": "ollama_chat-qwen:0.5b",
"model_type": "ollama_chat",
"model_name": "qwen:0.5b",
"options": {
"temperature": 0.7
},
"keep_alive": "5m"
},
{
"config_name": "ollama_chat-codellama",
"model_type": "ollama_chat",
"model_name": "codellama",
"options": {
"temperature": 0.7
},
"keep_alive": "5m"
}
]

View File

@@ -0,0 +1,28 @@
[{
"config_name": "ollama_embedding-llama2",
"model_type": "ollama_embedding",
"model_name": "llama2",
"options": {
"temperature": 0.7
},
"keep_alive": "5m"
},
{
"config_name": "ollama_embedding-mistral",
"model_type": "ollama_embedding",
"model_name": "mistral",
"options": {
"temperature": 0.7
},
"keep_alive": "5m"
},
{
"config_name": "ollama_embedding-qwen:0.5b",
"model_type": "ollama_embedding",
"model_name": "qwen:0.5b",
"options": {
"temperature": 0.7
},
"keep_alive": "5m"
}
]

View File

@@ -0,0 +1,28 @@
[{
"config_name": "ollama_generate-llama2",
"model_type": "ollama_generate",
"model_name": "llama2",
"options": {
"temperature": 0.7
},
"keep_alive": "5m"
},
{
"config_name": "ollama_generate-mistral",
"model_type": "ollama_generate",
"model_name": "mistral",
"options": {
"temperature": 0.7
},
"keep_alive": "5m"
},
{
"config_name": "ollama_generate-qwen:0.5b",
"model_type": "ollama_generate",
"model_name": "qwen:0.5b",
"options": {
"temperature": 0.7
},
"keep_alive": "5m"
}
]

View File

@@ -0,0 +1,38 @@
[
{
"config_name": "openai_chat_gpt-4",
"model_type": "openai_chat",
"model_name": "gpt-4",
"api_key": "{your_api_key}",
"client_args": {
"max_retries": 3
},
"generate_args": {
"temperature": 0.7
}
},
{
"config_name": "openai_chat_gpt-3.5-turbo",
"model_type": "openai_chat",
"model_name": "gpt-3.5-turbo",
"api_key": "{your_api_key}",
"client_args": {
"max_retries": 3
},
"generate_args": {
"temperature": 0.7
}
},
{
"config_name": "openai_chat_gpt-4o",
"model_type": "openai_chat",
"model_name": "gpt-4o",
"api_key": "{your_api_key}",
"client_args": {
"max_retries": 3
},
"generate_args": {
"temperature": 0.7
}
}
]

View File

@@ -0,0 +1,27 @@
[{
"config_name": "openai_dall_e-dall-e-2",
"model_type": "openai_dall_e",
"model_name": "dall-e-2",
"api_key": "{your_api_key}",
"client_args": {
"max_retries": 3
},
"generate_args": {
"n": 1,
"size": "512x512"
}
},
{
"config_name": "openai_dall_e-dall-e-3",
"model_type": "openai_dall_e",
"model_name": "dall-e-3",
"api_key": "{your_api_key}",
"client_args": {
"max_retries": 3
},
"generate_args": {
"n": 1,
"size": "512x512"
}
}
]

View File

@@ -0,0 +1,13 @@
[{
"config_name": "openai_embedding-text-embedding-ada-002",
"model_type": "openai_embedding",
"model_name": "text-embedding-ada-002",
"api_key": "{your_api_key}",
"client_args": {
"max_retries": 3
},
"generate_args": {
"temperature": 0.7
}
}
]

View File

@@ -0,0 +1,81 @@
[{
"config_name": "post_api_chat-flask_llama2-7b-chat-hf",
"model_type": "post_api_chat",
"api_url": "http://127.0.0.1:8000/llm/",
"json_args": {
"max_length": 4096,
"temperature": 0.5
}
},
{
"config_name": "post_api_chat-flask_llama2-7b-chat-ms",
"model_type": "post_api_chat",
"api_url": "http://127.0.0.1:8000/llm/",
"json_args": {
"max_length": 4096,
"temperature": 0.5
}
},
{
"config_name": "post_api-fastchat_llama2-7b-chat-hf",
"model_type": "openai_chat",
"api_key": "EMPTY",
"client_args": {
"base_url": "http://127.0.0.1:8000/v1/"
},
"generate_args": {
"temperature": 0.5
}
},
{
"config_name": "post_api-vllm_llama2-7b-chat-hf",
"model_type": "openai_chat",
"api_key": "EMPTY",
"client_args": {
"base_url": "http://127.0.0.1:8000/v1/"
},
"generate_args": {
"temperature": 0.5
}
},
{
"config_name": "post_api_chat-model-inference-api-gpt2",
"model_type": "post_api_chat",
"headers": {
"Authorization": "Bearer {YOUR_API_TOKEN}"
},
"api_url": "https://api-inference.huggingface.co/models/gpt2"
},
{
"config_name": "post_api_img_syn_config",
"model_type": "post_api_dall_e",
"api_url": "http://xxx.xxx.xxx.xxx:xxxx/xxx",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer {YOUR_API_TOKEN}"
},
"messages_key": "prompt",
"json_args": {
"model": "{YOUR_MODEL}",
"n":1,
"quality":"standard",
"response_format":"url",
"size":"512x512",
"style":"natural"
}
},
{
"config_name": "post_api_embedding_config",
"model_type": "post_api_embedding",
"api_url": "http://xxx.xxx.xxx.xxx:xxxx/xxx",
"headers": {
"Content-Type": "application/json",
"Authorization": "Bearer {YOUR_API_TOKEN}"
},
"messages_key": "input",
"json_args": {
"model": "{YOUR_MODEL}",
"encoding_format": "float"
}
}
]

View File

@@ -0,0 +1,7 @@
[{
"config_name": "zhipuai_chat-glm",
"model_type": "zhipuai_chat",
"model_name": "glm-4",
"api_key": "{your_api_key}"
}
]

View File

@@ -0,0 +1,7 @@
[{
"config_name": "zhipu-embedding",
"model_type": "zhipuai_embedding",
"model_name": "embedding-2",
"api_key": "{your_api_key}"
}
]

View File

@@ -0,0 +1,136 @@
# Llama3 in AgentScope
AgentScope supports Llama3 now! You can
- 🚀 Set up Llama3 model service in AgentScope! Both CPU and GPU inference are supported!
- 🔧 Test Llama3 in AgentScope built-in examples!
- 🖋 Use Llama3 to build your own multi-agent applications!
Follow the guidance below to use Llama3 in AgentScope!
## Contents
- [CPU Inference](#cpu-inference)
- [Setup Llama3 Service](#setup-llama3-service)
- [Use Llama3 in AgentScope](#use-llama3-in-agentscope)
- [GPU Inference](#gpu-inference)
- [Setup Llama3 Service](#setup-llama3-service-1)
- [Use Llama3 in AgentScope](#use-llama3-in-agentscope-1)
## CPU Inference
### Setup Llama3 Service
AgentScope supports Llama3 CPU inference with the help of ollama. Note the llama3 models in ollama are quantized into 4 bits.
1. Download ollama from [here](https://ollama.com/).
2. Start ollama software, or execute the following command in terminal
```bash
ollama serve
```
3. Pull llama3 model by the following command
```bash
# llama3 8b model
ollama pull llama3
# llama3 70b model
ollama pull llama3:70b
```
### Use Llama3 in AgentScope
Use llama3 model with the following model configuration in AgentScope
```python
llama3_8b_ollama_model_configuration = {
"config_name": "ollama_llama3_8b",
"model_type": "ollama_chat",
"model_name": "llama3",
"options": {
"temperature": 0.5,
"seed": 123
},
"keep_alive": "5m"
}
llama3_70b_ollama_model_configuration = {
"config_name": "ollama_llama3_70b",
"model_type": "ollama_chat",
"model_name": "llama3:70b",
"options": {
"temperature": 0.5,
"seed": 123
},
"keep_alive": "5m"
}
```
After that, you can experience llama3 with our built-in examples! For example, start a conversation with llama3-8b model by the following code:
```python
import agentscope
from agentscope.agents import UserAgent, DialogAgent
agentscope.init(model_configs=llama3_8b_ollama_model_configuration)
user = UserAgent("user")
agent = DialogAgent("assistant", sys_prompt="You're a helpful assistant.", model_config_name="ollama_llama3_8b")
x = None
while True:
x = agent(x)
x = user(x)
if x.content == "exit":
break
```
## GPU Inference
### Setup Llama3 Service
If you have a GPU, you can set up llama3 model service with the help of Flask and Transformers quickly.
Note you need to apply for permission to download the llama3 model from [Hugging Face model hub](https://huggingface.co/unsloth/llama-3-8b-Instruct).
1. Install Flask and Transformers
```bash
pip install flask transformers torch
```
2. Apply for model permission, and log in your huggingface account in terminal
```bash
huggingface-cli login
```
3. Then run flask server by the following command in scripts directory:
```bash
# 8B model
python flask_transformers/setup_hf_service.py --model_name_or_path meta-llama/Meta-Llama-3-8B-Instruct --port 8000
# 70B model
python flask_transformers/setup_hf_service.py --model_name_or_path meta-llama/Meta-Llama-3-70B-Instruct --port 8000
```
### Use Llama3 in AgentScope
In AgentScope, use the following model configurations
```python
llama3_flask_model_configuration = {
"model_type": "post_api_chat",
"config_name": "llama-3",
"api_url": "http://127.0.0.1:8000/llm/",
"json_args": {
"max_length": 4096,
"temperature": 0.5,
"eos_token_id": [128001, 128009] # currently the model configuration in huggingface misses eos_token_id
}
}
```

View File

@@ -0,0 +1,9 @@
{
"model_type": "post_api_chat",
"config_name": "llama-3",
"api_url": "http://127.0.0.1:8000/llm/",
"json_args": {
"max_length": 4096,
"temperature": 0.5
}
}

View File

@@ -0,0 +1,22 @@
[
{
"config_name": "ollama_llama3_8b",
"model_type": "ollama_chat",
"model_name": "llama3",
"options": {
"temperature": 0.5,
"seed": 123
},
"keep_alive": "5m"
},
{
"config_name": "ollama_llama3_70b",
"model_type": "ollama_chat",
"model_name": "llama3:70b",
"options": {
"temperature": 0.5,
"seed": 123
},
"keep_alive": "5m"
}
]