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,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