Skip to content

Commit

Permalink
add examples on how to use qwen-agent
Browse files Browse the repository at this point in the history
  • Loading branch information
tuhahaha authored and JianxinMa committed Jan 8, 2024
1 parent 770a4cd commit 853b3e7
Show file tree
Hide file tree
Showing 15 changed files with 170 additions and 45 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,4 @@ qwen_agent.egg-info/*
build/*
dist/*

demo/*
examples/*.ipynb
56 changes: 56 additions & 0 deletions examples/chess.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
from qwen_agent.prompts import RolePlay

# config
player = '你扮演一个玩五子棋的高手,你下{color}。棋盘中用0代表无棋子、用1黑棋、用-1白棋。用坐标<i,j>表示位置,i代表行,j代表列,棋盘左上角位置为<0,0>,请决定你要下在哪里,你可以随意下到一个位置,不要说你是AI助手不会下!返回格式为坐标:\n<i,j>\n除了这个坐标,不要返回其他任何内容'
role_black = player.format(color='黑棋')
role_write = player.format(color='白棋')
board = '你扮演一个五子棋棋盘,你可以调用代码工具,根据原始棋盘和玩家下棋的位置坐标,把新的棋盘用矩阵展示出来。棋盘中用0代表无棋子、用1表示黑棋、用-1表示白棋。用坐标<i,j>表示位置,i代表行,j代表列,棋盘左上角位置为<0,0>。'
llm_config = {'model': 'qwen-max', 'api_key': '', 'model_server': 'dashscope'}
function_list = ['code_interpreter']

# init three agent
player_black = RolePlay(llm=llm_config, system_instruction=role_black)
player_white = RolePlay(llm=llm_config, system_instruction=role_write)
board = RolePlay(function_list=function_list,
llm=llm_config,
system_instruction=board)


# define a result_processing function
def result_processing(response):
text = ''
for chunk in response:
text += chunk
print(text)

if '\nObservation:' in text:
# we need the chessboard in this case
text = text.split('\nObservation:')[-1].split('\nAnswer:')[0]
return text


# run agent: init chessboard
board_display = ''
position = ''
response = board.run('初始化一个10*10的棋盘')

while True:
# result processing: Extract the chessboard from the answer
board_display = result_processing(response)

# run agent: play black chess
response = player_black.run(f'当前棋盘是:\n{board_display}\n,请下黑棋', )
position = result_processing(response)

# run agent: update chessboard
response = board.run(
f'请更新棋盘并打印,原始棋盘是:\n{board_display}\n黑棋玩家刚下在{position}位置')
board_display = result_processing(response)

# run agent: play white chess
response = player_white.run(f'当前棋盘是:\n{board_display}\n,请下白棋:', )
position = result_processing(response)

# run agent: update chessboard
response = board.run(
f'请更新棋盘并打印,原始棋盘是:\n{board_display}\n白棋玩家刚下在{position}位置')
20 changes: 20 additions & 0 deletions examples/weather_bot.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
from qwen_agent.prompts import RolePlay

# config
role = '你扮演一个天气预报助手,你需要查询相应地区的天气,同时调用给你的画图工具绘制一张城市的图。'
llm_config = {'model': 'qwen-max', 'api_key': '', 'model_server': 'dashscope'}
function_list = ['image_gen', 'amap_weather']

# init agent
bot = RolePlay(function_list=function_list,
llm=llm_config,
system_instruction=role)

# run agent
response = bot.run('朝阳区天气')

# result processing
text = ''
for chunk in response:
text += chunk
print(text)
21 changes: 15 additions & 6 deletions qwen_agent/agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@
class Agent(ABC):

def __init__(self,
function_list: Optional[List[str]] = None,
function_list: Optional[List[Union[str, Dict]]] = None,
llm: Optional[Union[Dict, BaseChatModel]] = None,
system_instruction: Optional[str] = None,
storage_path: Optional[str] = None,
name: Optional[str] = None,
description: Optional[str] = None,
**kwargs):
"""
init tools/llm for one agent
:param function_list: Optional[List[str]] : a list of tool names
:param function_list: Optional[List[Union[str, Dict]]] :
(1)When str: tool names
(2)When Dict: tool cfg
:param llm: Optional[Union[Dict, BaseChatModel]]:
(1) When Dict: set the config of llm as {'model': '', 'api_key': '', 'model_server': ''}
(2) When BaseChatModel: llm is sent by another agent
Expand All @@ -45,6 +48,7 @@ def __init__(self,
self.mem = None
self.name = name
self.description = description
self.system_instruction = system_instruction or 'You are a helpful assistant.'

def run(self, *args, **kwargs) -> Union[str, Iterator[str]]:
if 'lang' not in kwargs:
Expand Down Expand Up @@ -78,16 +82,21 @@ def _call_tool(self, tool_name: str, tool_args: str, **kwargs):
"""
return self.function_map[tool_name].call(tool_args, **kwargs)

def _register_tool(self, tool_name: str):
def _register_tool(self, tool: Union[str, Dict]):
"""
Instantiate the global tool for the agent
"""
tool_name = tool
tool_cfg = None
if isinstance(tool, Dict):
tool_name = tool['name']
tool_cfg = tool
if tool_name not in TOOL_REGISTRY:
raise NotImplementedError
if tool_name not in self.function_list:
self.function_list.append(tool_name)
self.function_map[tool_name] = TOOL_REGISTRY[tool_name]()
if tool not in self.function_list:
self.function_list.append(tool)
self.function_map[tool_name] = TOOL_REGISTRY[tool_name](tool_cfg)

def _detect_tool(self, message: Union[str, dict]):
# use built-in default judgment functions
Expand Down
2 changes: 1 addition & 1 deletion qwen_agent/agents/article_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
class ArticleAgent(Agent):

def __init__(self,
function_list: Optional[List[str]] = None,
function_list: Optional[List[Union[str, Dict]]] = None,
llm: Optional[Union[Dict, BaseChatModel]] = None,
storage_path: Optional[str] = None,
name: Optional[str] = None,
Expand Down
2 changes: 1 addition & 1 deletion qwen_agent/agents/docqa_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
class DocQAAgent(Agent):

def __init__(self,
function_list: Optional[List[str]] = None,
function_list: Optional[List[Union[str, Dict]]] = None,
llm: Optional[Union[Dict, BaseChatModel]] = None,
storage_path: Optional[str] = None,
name: Optional[str] = None,
Expand Down
2 changes: 1 addition & 1 deletion qwen_agent/llm/qwen_dashscope.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,7 @@ def build_raw_prompt(self, messages):
im_start = '<|im_start|>'
im_end = '<|im_end|>'
if messages[0]['role'] == 'system':
sys = messages[0]['role']
sys = messages[0]['content']
prompt = f'{im_start}system\n{sys}{im_end}'
else:
prompt = f'{im_start}system\nYou are a helpful assistant.{im_end}'
Expand Down
2 changes: 1 addition & 1 deletion qwen_agent/memory/memory.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ class Memory(Agent):
"""

def __init__(self,
function_list: Optional[List[str]] = None,
function_list: Optional[List[Union[str, Dict]]] = None,
llm: Optional[Union[Dict, BaseChatModel]] = None,
storage_path: Optional[str] = None,
name: Optional[str] = None,
Expand Down
24 changes: 10 additions & 14 deletions qwen_agent/prompts/role_play.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
from typing import Dict, List, Optional

import jsonlines

from qwen_agent import Agent

TOOL_TEMPLATE_ZH = """# 工具
Expand All @@ -15,7 +13,7 @@
Action: 工具名称,必须是[{tool_names}]之一
Action Input: 工具输入
Observation: 工具结果
Final Answer: 根据工具结果进行回复,如果存在url,请使用如何格式展示出来:![图片](url)
Answer: 根据工具结果进行回复,如果存在url,请使用如下格式展示出来:![图片](url)
"""

Expand Down Expand Up @@ -54,7 +52,7 @@
Action: The tool to use, should be one of [{tool_names}]
Action Input: The input of the tool, need to be formatted as a JSON
Observation: The result returned by the tool
Final Answer: Summarize the results based on the Observation
Answer: Summarize the results based on the Observation
"""

Expand Down Expand Up @@ -89,22 +87,20 @@
'en': '(You have access to tools: [{tool_names}])',
}

ACTION_TOKEN = '\nAction:'
ARGS_TOKEN = '\nAction Input:'
OBSERVATION_TOKEN = '\nObservation:'
ANSWER_TOKEN = '\nFinal Answer:'
ACTION_TOKEN = 'Action:'
ARGS_TOKEN = 'Action Input:'
OBSERVATION_TOKEN = 'Observation:'
ANSWER_TOKEN = 'Answer:'


class RolePlay(Agent):

def _run(self,
user_request,
response_to_continue: str = None,
role_prompt: str = None,
history: Optional[List[Dict]] = None,
ref_doc: str = None,
lang: str = 'zh'):
self.role_prompt = role_prompt

self.tool_descs = '\n\n'.join(tool.function_plain_text
for tool in self.function_map.values())
Expand All @@ -122,7 +118,7 @@ def _run(self,
self.query_prefix = SPECIAL_PREFIX_TEMPLATE[lang].format(
tool_names=self.tool_names)
self.system_prompt += PROMPT_TEMPLATE[lang].format(
role_prompt=self.role_prompt)
role_prompt=self.system_instruction)

# Concat the system as one round of dialogue
messages = ([{
Expand Down Expand Up @@ -152,8 +148,8 @@ def _run(self,
})
messages.append({'role': 'assistant', 'content': ''})

with jsonlines.open('log.jsonl', mode='a') as writer:
writer.write(messages)
# with jsonlines.open('log.jsonl', mode='a') as writer:
# writer.write(messages)

planning_prompt = self.llm.build_raw_prompt(messages)
if response_to_continue:
Expand All @@ -171,7 +167,7 @@ def _run(self,
yield output
if use_tool:
observation = self._call_tool(action, action_input)
observation = f'\nObservation: {observation}\nFinal Answer:'
observation = f'\nObservation: {observation}\nAnswer:'
yield observation
planning_prompt += output + observation
else:
Expand Down
43 changes: 41 additions & 2 deletions qwen_agent/tools/amap_weather.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
import os
from typing import Dict, Optional

import pandas as pd
import requests

from qwen_agent.tools.base import BaseTool, register_tool


Expand All @@ -8,12 +14,45 @@ class AmapWeather(BaseTool):
parameters = [{
'name': 'location',
'type': 'string',
'description': '待查询天气的城市'
'description': 'get temperature for a specific location',
'required': True
}]

def __init__(self, cfg: Optional[Dict] = None):
super().__init__(cfg)

# remote call
self.url = 'https://restapi.amap.com/v3/weather/weatherInfo?city={city}&key={key}'
self.city_df = pd.read_excel(
'https://modelscope.oss-cn-beijing.aliyuncs.com/resource/agent/AMap_adcode_citycode.xlsx'
)

self.token = self.cfg.get('token', os.environ.get('AMAP_TOKEN', ''))
assert self.token != '', 'weather api token must be acquired through ' \
'https://lbs.amap.com/api/webservice/guide/create-project/get-key and set by AMAP_TOKEN'

def get_city_adcode(self, city_name):
filtered_df = self.city_df[self.city_df['中文名'] == city_name]
if len(filtered_df['adcode'].values) == 0:
raise ValueError(
f'location {city_name} not found, availables are {self.city_df["中文名"]}'
)
else:
return filtered_df['adcode'].values[0]

def call(self, params: str, **kwargs) -> str:
params = self._verify_args(params)
if isinstance(params, str):
return 'Parameter Error'

raise NotImplementedError
location = params['location']
response = requests.get(
self.url.format(city=self.get_city_adcode(location),
key=self.token))
data = response.json()
if data['status'] == '0':
raise RuntimeError(data)
else:
weather = data['lives'][0]['weather']
temperature = data['lives'][0]['temperature']
return f'{location}的天气是{weather}温度是{temperature}度。'
11 changes: 5 additions & 6 deletions qwen_agent/tools/base.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from abc import ABC, abstractmethod
from typing import Dict, List, Union
from typing import Dict, List, Optional, Union

import json5

Expand All @@ -23,12 +23,11 @@ class BaseTool(ABC):
description: str
parameters: List[Dict]

def __init__(self, schema: str = 'oai'):
"""
def __init__(self, cfg: Optional[Dict] = None):
self.cfg = cfg or {}

:param schema: Format of tools, default to oai format, in case there is a need for other formats
"""
self.schema = schema
# schema: Format of tools, default to oai format, in case there is a need for other formats
self.schema = self.cfg.get('schema', 'oai')
self.function = self._build_function()
self.function_plain_text = self._parser_function()

Expand Down
18 changes: 13 additions & 5 deletions qwen_agent/tools/doc_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,15 +131,22 @@ def process_file(url: str, content: str, source: str, db: Storage = None):

meta_info = db.get('meta_info')
if meta_info == 'Not Exist':
meta_info = []
meta_info = {}
else:
meta_info = json5.loads(meta_info)
meta_info.append({
if isinstance(meta_info, list):
logger.info('update meta_info to new format')
new_meta_info = {}
for x in meta_info:
new_meta_info[x['url']] = x
meta_info = new_meta_info

meta_info[url] = {
'url': url,
'time': now_time,
'title': title,
'checked': True,
})
}
db.put('meta_info', json.dumps(meta_info, ensure_ascii=False))

return new_record_str
Expand Down Expand Up @@ -171,6 +178,7 @@ def read_data_by_condition(db: Storage = None, **kwargs):
records = []
else:
records = json5.loads(meta_info)
records = records.values()

if 'time_limit' in kwargs:
filter_records = []
Expand Down Expand Up @@ -214,7 +222,6 @@ class DocParser(BaseTool):
def call(self,
params: str,
db: Storage = None,
re_parse: bool = False,
raw: bool = False,
**kwargs) -> str:
params = self._verify_args(params)
Expand All @@ -227,7 +234,8 @@ def call(self,
record = None
if 'url' in params:
record = db.get(params['url'])
if re_parse or record == 'Not Exist':
# need to parse and save doc
if 'content' in kwargs:
record = process_file(url=params['url'],
content=kwargs['content'],
source=kwargs['type'],
Expand Down
Loading

0 comments on commit 853b3e7

Please sign in to comment.