[User] Show me the code for 'qwen_agent\agents\fncall_agent.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
from typing import Dict, Iterator, List, Literal, Optional, Union

from qwen_agent import Agent
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import DEFAULT_SYSTEM_MESSAGE, FUNCTION, Message
from qwen_agent.memory import Memory
from qwen_agent.settings import MAX_LLM_CALL_PER_RUN
from qwen_agent.tools import BaseTool
from qwen_agent.utils.utils import extract_files_from_messages

class FnCallAgent(Agent):
    """This is a widely applicable function call agent integrated with llm and tool use ability."""

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 name: Optional[str] = None,
                 description: Optional[str] = None,
                 files: Optional[List[str]] = None,
                 **kwargs):
        """Initialization the agent.

        Args:
            function_list: One list of tool name, tool configuration or Tool object,
              such as 'code_interpreter', {'name': 'code_interpreter', 'timeout': 10}, or CodeInterpreter().
            llm: The LLM model configuration or LLM model object.
              Set the configuration as {'model': '', 'api_key': '', 'model_server': ''}.
            system_message: The specified system message for LLM chat.
            name: The name of this agent.
            description: The description of this agent, which will be used for multi_agent.
            files: A file url list. The initialized files for the agent.
        """
        super().__init__(function_list=function_list,
                         llm=llm,
                         system_message=system_message,
                         name=name,
                         description=description)

        if not hasattr(self, 'mem'):
            # Default to use Memory to manage files
            if 'qwq' in self.llm.model.lower() or 'qvq' in self.llm.model.lower() or 'qwen3' in self.llm.model.lower():
                if 'dashscope' in self.llm.model_type:
                    mem_llm = {
                        'model': 'qwen-turbo',
                        'model_type': 'qwen_dashscope',
                        'generate_cfg': {
                            'max_input_tokens': 30000
                        }
                    }
                else:
                    mem_llm = None
            else:
                mem_llm = self.llm
            self.mem = Memory(llm=mem_llm, files=files, **kwargs)

    def _run(self, messages: List[Message], lang: Literal['en', 'zh'] = 'en', **kwargs) -> Iterator[List[Message]]:
        messages = copy.deepcopy(messages)
        num_llm_calls_available = MAX_LLM_CALL_PER_RUN
        response = []
        while True and num_llm_calls_available > 0:
            num_llm_calls_available -= 1

            extra_generate_cfg = {'lang': lang}
            if kwargs.get('seed') is not None:
                extra_generate_cfg['seed'] = kwargs['seed']
            output_stream = self._call_llm(messages=messages,
                                           functions=[func.function for func in self.function_map.values()],
                                           extra_generate_cfg=extra_generate_cfg)
            output: List[Message] = []
            for output in output_stream:
                if output:
                    yield response + output
            if output:
                response.extend(output)
                messages.extend(output)
                used_any_tool = False
                for out in output:
                    use_tool, tool_name, tool_args, _ = self._detect_tool(out)
                    if use_tool:
                        tool_result = self._call_tool(tool_name, tool_args, messages=messages, **kwargs)
                        fn_msg = Message(role=FUNCTION,
                                         name=tool_name,
                                         content=tool_result,
                                         extra={'function_id': out.extra.get('function_id', '1')})
                        messages.append(fn_msg)
                        response.append(fn_msg)
                        yield response
                        used_any_tool = True
                if not used_any_tool:
                    break
        yield response

    def _call_tool(self, tool_name: str, tool_args: Union[str, dict] = '{}', **kwargs) -> str:
        if tool_name not in self.function_map:
            return f'Tool {tool_name} does not exists.'
        # Temporary plan: Check if it is necessary to transfer files to the tool
        # Todo: This should be changed to parameter passing, and the file URL should be determined by the model
        if self.function_map[tool_name].file_access:
            assert 'messages' in kwargs
            files = extract_files_from_messages(kwargs['messages'], include_images=True) + self.mem.system_files
            return super()._call_tool(tool_name, tool_args, files=files, **kwargs)
        else:
            return super()._call_tool(tool_name, tool_args, **kwargs)

```


[User] Show me the code for 'qwen_agent\agents\group_chat.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
import random
from typing import Dict, Iterator, List, Optional, Union

from qwen_agent import Agent, MultiAgentHub
from qwen_agent.agents.assistant import Assistant
from qwen_agent.agents.group_chat_auto_router import GroupChatAutoRouter
from qwen_agent.agents.user_agent import PENDING_USER_INPUT, UserAgent
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import Message
from qwen_agent.log import logger
from qwen_agent.tools import BaseTool

class GroupChat(Agent, MultiAgentHub):
    """This is an agent for multi-agent management.

    This agent can accept a list of agents, manage their speaking order, and output the response of each agent.
    """

    _VALID_AGENT_SELECTION_METHODS = ['manual', 'round_robin', 'random', 'auto']

    def __init__(self,
                 agents: Union[List[Agent], Dict],
                 agent_selection_method: Optional[str] = 'auto',
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 **kwargs):
        """Initialization the agent.

        Args:
            agents: A list of agents of agent configurations. One configuration example is:
              {
                'background': 'An interest group',
                'agents': [{
                        'name': 'Tang Xiao',
                        'description': 'A hardworking worker, addicted to work every day, gradually losing weight.',
                        'is_human': True  # mark this as a real person
                    }, {
                        'name': 'Tou Da',
                        'description': 'A sports student',
                        'instructions': 'You are a sports student who loves sports.',
                        'knowledge_files': ['http://example.html'],
                        'selected_tools': ['image_gen']
                    }]
              }
            agent_selection_method: The method of select speaker:
              (1) auto: Using one host agent to choose the speaker according to the context.
              (2) round_robin: Speak in order.
              (3) random: Random speech.
            function_list: The tools for inputting to the host.
            llm: The LLM for inputting to the host.
        """
        super().__init__(**kwargs)
        assert agent_selection_method in self._VALID_AGENT_SELECTION_METHODS, f'You must choose agent_selection_method from {", ".join(self._VALID_AGENT_SELECTION_METHODS)}'
        self.agent_selection_method = agent_selection_method

        if isinstance(agents, dict):
            self._agents = self._init_agents_from_config(agents, llm=llm)
        else:
            self._agents = agents

        if self.agent_selection_method == 'auto':
            assert llm is not None, 'Need to provide LLM to the host in auto mode'
            self.host = GroupChatAutoRouter(function_list=function_list, llm=llm, agents=self.agents, name='host')

    def _run(self,
             messages: List[Message] = None,
             lang: str = 'zh',
             max_round: Optional[int] = 3,
             need_batch_response: bool = True,
             mentioned_agents_name: List[str] = None,
             **kwargs) -> Iterator[List[Message]]:

        messages = copy.deepcopy(messages)
        for message in messages:
            if message.role == 'assistant':
                assert message.name, 'In group chat, each agent must be given a name'
            # Name will be used for router
            # Todo: Dealing with situations where there are no real players
            if not message.name:
                message.name = message.role

        if need_batch_response:
            return self._gen_batch_response(messages=messages,
                                            lang=lang,
                                            max_round=max_round,
                                            mentioned_agents_name=mentioned_agents_name,
                                            **kwargs)
        else:
            return self._gen_one_response(messages=messages,
                                          lang=lang,
                                          mentioned_agents_name=mentioned_agents_name,
                                          **kwargs)

    def _gen_batch_response(self,
                            messages: List[Message] = None,
                            lang: str = 'zh',
                            max_round: Optional[int] = 3,
                            mentioned_agents_name: List[str] = None,
                            **kwargs) -> Iterator[List[Message]]:
        # Record all mentioned agents: reply in order
        mentioned_agents_name = mentioned_agents_name or []
        messages = copy.deepcopy(messages)

        response = []
        for i in range(max_round):
            if isinstance(messages[-1].content, list):
                content = '\n'.join([x.text if x.text else '' for x in messages[-1].content]).strip()
            else:
                content = messages[-1].content.strip()
            if '@' in content:
                for x in content.split('@'):
                    for agent in self.agents:
                        if x.startswith(agent.name):
                            if agent not in mentioned_agents_name:
                                mentioned_agents_name.append(agent.name)
                            break
            rsp = []
            for rsp in self._gen_one_response(messages=messages,
                                              lang=lang,
                                              mentioned_agents_name=mentioned_agents_name,
                                              **kwargs):
                yield response + rsp
            if not rsp:
                # The topic ends
                break
            if mentioned_agents_name:
                assert rsp[-1].name == mentioned_agents_name[0]
                mentioned_agents_name.pop(0)

            response += rsp
            if rsp[-1].content == PENDING_USER_INPUT:
                # Terminate group chat and wait for user input
                break
            messages.extend(rsp)
        yield response

    def _gen_one_response(self,
                          messages: List[Message] = None,
                          lang: str = 'zh',
                          mentioned_agents_name: List[str] = None,
                          **kwargs) -> Iterator[List[Message]]:

        selected_agent = self._select_agent(messages, mentioned_agents_name, lang)
        if selected_agent:
            logger.info(f'selected_agent_name: {selected_agent.name}')
            new_messages = self._manage_messages(messages, selected_agent.name)
            for rsp in selected_agent.run(messages=new_messages, **kwargs):
                yield rsp
        else:
            yield []

    def _select_agent(self,
                      messages: List[Message],
                      mentioned_agents_name: List[str] = None,
                      lang: str = 'zh') -> Union[Agent, None]:
        agents_map = {x.name: x for x in self.agents}
        if mentioned_agents_name:
            # Manually select agent
            return agents_map[mentioned_agents_name[0]]

        if self.agent_selection_method == 'auto':
            *_, last = self.host.run(messages=messages, lang=lang)
            auto_selected_agent = None
            if isinstance(last[-1]['content'], str):
                auto_selected_agent = last[-1]['content']
            else:
                assert isinstance(last[-1]['content'], list)
                if 'text' in last[-1]['content'][0]:
                    auto_selected_agent = last[-1]['content'][0]['text']
            if auto_selected_agent in agents_map.keys():
                return agents_map[auto_selected_agent]
            elif auto_selected_agent == '[STOP]':
                return None

        if self.agent_selection_method == 'random':
            agent = random.choice(list(self.agents))
            return agent

        if self.agent_selection_method == 'manual':
            for i in range(3):
                agent_key = input('Please enter the selected agent name: ')
                if agent_key in agents_map.keys():
                    return agents_map[agent_key]
                else:
                    logger.warning(f'Please select one agent from {str(list(agents_map.keys()))}')

        # round_robin
        if messages:
            agents_list = [x.name for x in self.agents]
            try:
                last_agent_index = agents_list.index(messages[-1]['name'])
            except ValueError:
                last_agent_index = -1
        else:
            last_agent_index = -1
        return self.agents[(last_agent_index + 1) % len(self.agents)]

    def _manage_messages(self, messages: List[Message], name: str) -> List[Message]:
        new_messages = []
        new_msg = None
        i = 0
        while i < len(messages):
            msg = messages[i]
            if msg.name == name:
                if new_msg:
                    # Have 'user' before 'assistant'
                    new_messages.append(new_msg)
                if not msg.function_call and (  # noqa
                    (not new_messages) or (new_messages[-1].name == name)):  # noqa
                    new_messages.append(Message('user', f'{name}: '))

                new_msg = copy.deepcopy(msg)
                new_msg.role = 'assistant'
                new_messages.append(new_msg)
                new_msg = None
                if msg.function_call:
                    # Append the function call msg
                    assert messages[i + 1].role == 'function'
                    new_messages.append(copy.deepcopy(messages[i + 1]))
                    i += 1
            else:
                if isinstance(msg.content, list):
                    content = '\n'.join([x.text if x.text else '' for x in msg.content]).strip()
                else:
                    content = msg.content.strip()

                if content.strip():
                    if not new_msg:
                        new_msg = Message('user', f'{msg.name}: {content.strip()}')
                    else:
                        new_msg.content += f'\n{msg.name}: {content.strip()}'

                if msg.function_call:
                    # Skip the function call msg
                    assert messages[i + 1].role == 'function'
                    assert messages[i + 2].role == 'assistant' and messages[i + 2].name == msg.name
                    i += 1

            i += 1
        if new_msg:
            new_messages.append(new_msg)

        if new_messages and new_messages[-1].role == 'user':
            new_messages[-1].content += f'\n{name}: '
        else:
            new_messages.append(Message('user', f'{name}: '))
        return new_messages

    def _init_agents_from_config(self, cfgs: Dict, llm: Optional[Union[Dict, BaseChatModel]] = None) -> List[Agent]:

        def _build_system_from_role_config(config: Dict):
            role_chat_prompt = """你是{name}。{description}\n\n{instructions}"""

            name = config.get('name', '').strip()
            description = config.get('description', '').lstrip('\n').rstrip()
            instructions = config.get('instructions', '').lstrip('\n').rstrip()
            if len(instructions) >= len(description):
                description = ''  # redundant, as we already have instructions
            else:
                description = f'你的简介是：{description}'
            prompt = role_chat_prompt.format(name=name, description=description, instructions=instructions)

            knowledge_files = config.get('knowledge_files', [])
            selected_tools = config.get('selected_tools', [])
            return prompt, knowledge_files, selected_tools

        agents = []
        groupchat_background = '你在一个群聊中，'
        if cfgs.get('background', ''):
            groupchat_background += f'群聊背景为：{cfgs["background"]}'

        for cfg in cfgs['agents']:
            system, knowledge_files, selected_tools = _build_system_from_role_config(cfg)
            if 'is_human' in cfg and cfg['is_human']:
                # Append human agent
                agents.append(UserAgent(name=cfg['name'], description=cfg['description']))
            else:
                # Create npc agent by config
                other_agents = []
                for x in cfgs['agents']:
                    if x['name'] != cfg['name']:
                        other_agents.append(x['name'])
                agents.append(
                    Assistant(llm=llm,
                              system_message=groupchat_background + system +
                              f'\n\n群里其他成员包括：{", ".join(other_agents)}，如果你想和别人对话，可以@成员名字。\n' +
                              '\n\n讲话时请直接输出内容，不要输出你的名字。\n\n其他群友的发言历史以如下格式展示：\n角色名: 说话内容',
                              files=knowledge_files,
                              function_list=selected_tools,
                              name=cfg['name'],
                              description=cfg['description']))
        return agents

```


[User] Show me the code for 'qwen_agent\agents\group_chat_auto_router.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
from typing import Dict, Iterator, List, Optional, Union

from qwen_agent import Agent
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import Message, SYSTEM
from qwen_agent.tools import BaseTool
from qwen_agent.utils.utils import has_chinese_chars

class GroupChatAutoRouter(Agent):
    PROMPT_TEMPLATE_ZH = '''你扮演角色扮演游戏的上帝，你的任务是选择合适的发言角色。有如下角色：
{agent_descs}

角色间的对话历史格式如下，越新的对话越重要：
角色名: 说话内容

请阅读对话历史，并选择下一个合适的发言角色，从 [{agent_names}] 里选，当真实用户最近表明了停止聊天时，或话题应该终止时，请返回“[STOP]”，用户很懒，非必要不要选真实用户。
仅返回角色名或“[STOP]”，不要返回其余内容。'''

    PROMPT_TEMPLATE_EN = '''You are in a role play game. The following roles are available:
{agent_descs}

The format of dialogue history between roles is as follows:
Role Name: Speech Content

Please read the dialogue history and choose the next suitable role to speak.
When the user indicates to stop chatting or when the topic should be terminated, please return '[STOP]'.
Only return the role name from [{agent_names}] or '[STOP]'. Do not reply any other content.'''

    PROMPT_TEMPLATE = {
        'zh': PROMPT_TEMPLATE_ZH,
        'en': PROMPT_TEMPLATE_EN,
    }

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 agents: List[Agent] = None,
                 name: Optional[str] = None,
                 description: Optional[str] = None,
                 **kwargs):
        # This agent need prepend special system message according to inputted agents
        agent_descs = '\n'.join([f'{x.name}: {x.description}' for x in agents])
        lang = 'en'
        if has_chinese_chars(agent_descs):
            lang = 'zh'
        system_prompt = self.PROMPT_TEMPLATE[lang].format(agent_descs=agent_descs,
                                                          agent_names=', '.join([x.name for x in agents]))

        super().__init__(function_list=function_list,
                         llm=llm,
                         system_message=system_prompt,
                         name=name,
                         description=description,
                         **kwargs)

    def _run(self, messages: List[Message], lang: str = 'en', **kwargs) -> Iterator[List[Message]]:
        dialogue = [] # convert existing messages into a prompt
        for msg in messages:
            if msg.role == SYSTEM:
                continue
            if msg.role == 'function' or not msg.content:
                continue
            if isinstance(msg.content, list):
                content = '\n'.join([x.text if x.text else '' for x in msg.content]).strip()
            else:
                content = msg.content.strip()
            display_name = msg.role
            if msg.name:
                display_name = msg.name
            if dialogue and dialogue[-1].startswith(display_name):
                dialogue[-1] += f'\n{content}'
            else:
                dialogue.append(f'{display_name}: {content}')

        if not dialogue:
            dialogue.append('对话刚开始，请任意选择一个发言人，别选真实用户')
        assert messages[0].role == SYSTEM
        new_messages = [copy.deepcopy(messages[0]), Message('user', '\n'.join(dialogue))]
        return self._call_llm(messages=new_messages)

```


[User] Show me the code for 'qwen_agent\agents\group_chat_creator.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
import json
from typing import Dict, Iterator, List, Optional, Tuple, Union

import json5

from qwen_agent import Agent
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import Message
from qwen_agent.tools import BaseTool

CONFIG_SCHEMA = {
    'name': '... # 角色名字，5字左右',
    'description': '... # 角色简介，10字左右',
    'instructions': '... # 对角色的具体功能要求，30字左右，以第二人称称呼角色'
}

CONFIG_EXAMPLE = {
    'name': '小红书写作专家',
    'description': '我会写小红书爆款',
    'instructions': '你是小红书爆款写作专家，创作会先产5个标题（含emoji），再产正文（每段落含emoji，文末有tag）。'
}

BACKGROUND_TOKEN = '<Background>'
CONFIG_TOKEN = '<Config>'
ANSWER_TOKEN = '<Answer>'

ROLE_CREATE_SYSTEM = '''你扮演创建群聊的助手，请你根据用户输入的聊天主题，创建n个合适的虚拟角色，这些角色将在一个聊天室内对话，你需要和用户进行对话，明确用户对这些角色的要求。

配置文件为json格式：
{config_schema}

一个优秀的RichConfig样例如下：
{config_example}

在接下来的对话中，请在回答时严格使用如下格式，先生成群聊背景，然后依次生成所有角色的配置文件，最后再作出回复，除此之外不要回复其他任何内容：
{background_token}: ... # 生成的群聊背景，包括人物关系，预设故事背景等信息。
{config_token}: ... # 生成的第一个角色的配置文件，严格按照以上json格式，禁止为空。保证name和description不为空。instructions内容比description具体，如果用户给出了详细指令，请完全保留，用第二人称描述角色，例如“你是xxx，你具有xxx能力。
{config_token}: ... # 生成的第二个角色的配置文件，要求同上。
...
{config_token}: ... # 生成的第n个角色的配置文件，要求同上，如果用户没有明确指出n的数量，则n等于3；要求每个角色的名字不相同。
{answer_token}: ... # 你希望对用户说的话，用于询问用户对角色的要求，禁止为空，问题要广泛，不要重复问类似的问题。

如果群聊背景或某个角色的配置文件不需要更新，可以不重复输出{background_token}和对应的{config_token}的内容、只输出{answer_token}和需要修改的{config_token}的内容。'''.format(
    config_schema=json.dumps(CONFIG_SCHEMA, ensure_ascii=False, indent=2),
    config_example=json.dumps(CONFIG_EXAMPLE, ensure_ascii=False, indent=2),
    background_token=BACKGROUND_TOKEN,
    config_token=CONFIG_TOKEN,
    answer_token=ANSWER_TOKEN,
)
assert CONFIG_TOKEN in ROLE_CREATE_SYSTEM
assert ANSWER_TOKEN in ROLE_CREATE_SYSTEM

class GroupChatCreator(Agent):

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 name: Optional[str] = None,
                 description: Optional[str] = None,
                 **kwargs):
        super().__init__(function_list=function_list,
                         llm=llm,
                         system_message=ROLE_CREATE_SYSTEM,
                         name=name,
                         description=description,
                         **kwargs)

    def _run(self,
             messages: List[Message],
             agents: List[Agent] = None,
             lang: str = 'en',
             **kwargs) -> Iterator[List[Message]]:
        messages = copy.deepcopy(messages)
        messages = self._preprocess_messages(messages)

        for rsp in self._call_llm(messages=messages):
            yield self._postprocess_messages(rsp)

    def _preprocess_messages(self, messages: List[Message]) -> List[Message]:
        new_messages = []
        content = []
        for message in messages:
            if message.role != 'assistant':
                new_messages.append(message)
            else:
                if message.name == 'background':
                    content.append(f'{BACKGROUND_TOKEN}: {message.content}')
                elif message.name == 'role_config':
                    content.append(f'{CONFIG_TOKEN}: {message.content}')
                else:
                    content.append(f'{ANSWER_TOKEN}: {message.content}')
                    assert new_messages[-1].role == 'user'
                    new_messages.append(Message('assistant', '\n'.join(content)))
                    content = []
        return new_messages

    def _postprocess_messages(self, messages: List[Message]) -> List[Message]:
        new_messages = []
        assert len(messages) == 1
        message = messages[-1]
        background, cfgs, answer = self._extract_role_config_and_answer(message.content)
        if background:
            new_messages.append(Message(message.role, background, name='background'))
        if cfgs:
            for cfg in cfgs:
                new_messages.append(Message(message.role, cfg, name='role_config'))

        new_messages.append(Message(message.role, answer, name=message.name))
        return new_messages

    def _extract_role_config_and_answer(self, text: str) -> Tuple[str, List[str], str]:
        background, cfgs, answer = '', [], ''
        back_pos, cfg_pos, ans_pos = text.find(f'{BACKGROUND_TOKEN}: '), text.find(f'{CONFIG_TOKEN}: '), text.find(
            f'{ANSWER_TOKEN}: ')

        if ans_pos > -1:
            answer = text[ans_pos + len(f'{ANSWER_TOKEN}: '):]
        else:
            ans_pos = len(text)

        if back_pos > -1:
            if cfg_pos > back_pos:
                background = text[back_pos + len(f'{BACKGROUND_TOKEN}: '):cfg_pos]
            else:
                background = text[back_pos + len(f'{BACKGROUND_TOKEN}: '):ans_pos]
        text = text[:ans_pos]

        tmp = text.split(f'{CONFIG_TOKEN}: ')
        for t in tmp:
            if t.strip():
                try:
                    _ = json5.loads(t.strip())
                    cfgs.append(t.strip())
                except Exception:
                    continue

        if not (background or cfgs or answer):
            # There should always be ANSWER_TOKEN, if not, treat the entire content as answer
            answer = text
        return background, cfgs, answer

```


[User] Show me the code for 'qwen_agent\agents\human_simulator.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
import random
from typing import Dict, Iterator, List, Optional, Union

from qwen_agent.agents import Agent
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import Message
from qwen_agent.tools import BaseTool

STOP = '<|STOP|>'

DEFAULT_HUMAN_SIMULATOR_PROMPT = """"Play a game where you act as the human user and the user acts as the AI assistant.

# Rules:
- Ask questions and make requests as a typical human user would.
- Your questions should be related to the provided context or the chat history.
- You should output a JSON list of four possible questions, and nothing more.
- The questions must be diverse in their complexity, intentions, and language usage.
- If you feel the conversation can end, please output ["%s"] directly without any other content.""" % STOP

class HumanSimulator(Agent):

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = None,
                 name: Optional[str] = None,
                 description: Optional[str] = None,
                 **kwargs):
        if system_message:
            system_message = DEFAULT_HUMAN_SIMULATOR_PROMPT + '\n\n' + system_message
        super().__init__(function_list=function_list,
                         llm=llm,
                         system_message=system_message,
                         name=name,
                         description=description,
                         **kwargs)

    def _run(self, messages: List[Message], lang: str = 'en', **kwargs) -> Iterator[List[Message]]:
        if (not messages) or (messages[0].role != 'user'):
            begin_msg = 'Please role-play as a human user and make your first request.\n\nBegin!'
            messages = [Message(role='user', content=begin_msg)] + messages
        *_, respones = self._call_llm(messages=messages)
        rng = random.Random(kwargs.get('seed', 42))
        try:
            text = rng.choice(json.loads(respones[-1].content))
            if (not isinstance(text, str)) or (not text):
                text = STOP
            respones[-1].content = text
        except json.decoder.JSONDecodeError:
            respones[-1].content = STOP
        yield respones

```


[User] Show me the code for 'qwen_agent\agents\memo_assistant.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
from typing import Dict, Iterator, List, Optional, Union

import json5

from qwen_agent.agents import Assistant
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import DEFAULT_SYSTEM_MESSAGE, SYSTEM, USER, ContentItem, Message
from qwen_agent.tools import BaseTool

MEMORY_PROMPT = """
在对话过程中，你可以随时使用storage工具来存储你认为需要记住的信息，同时也随时可以读取曾经可能存储了的历史信息。
这将有助于你在和用户的长对话中，记住某些重要的信息，比如用户的喜好、特殊信息、或重大事件等。
关于数据存取，有以下两点建议：
1. 存一条数据的key尽量简洁易懂，可以用所记录内容的关键词；
2. 如果忘记存过什么数据，可以使用scan查看记录过哪些数据；

此处展示当前你存入的所有信息，因此你可以省去专门读取数据的操作：
<info>
{storage_info}
</info>

你的记忆很短暂，请频繁的调用工具存储或读取重要对话内容。
"""

class MemoAssistant(Assistant):

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 name: Optional[str] = None,
                 description: Optional[str] = None,
                 files: Optional[List[str]] = None):
        function_list = function_list or []
        super().__init__(function_list=['storage'] + function_list,
                         llm=llm,
                         system_message=system_message,
                         name=name,
                         description=description,
                         files=files)

    def _run(self, messages: List[Message], lang: str = 'zh', knowledge: str = '', **kwargs) -> Iterator[List[Message]]:
        new_message = self._prepend_storage_info_to_sys(messages)
        new_message = self._truncate_dialogue_history(new_message)

        for rsp in super()._run(new_message, lang=lang, knowledge=knowledge, **kwargs):
            yield rsp

    def _prepend_storage_info_to_sys(self, messages: List[Message]) -> List[Message]:
        messages = copy.deepcopy(messages)
        all_kv = {}
        # Obtained from message, with the purpose of facilitating control of information volume
        for msg in messages:
            if msg.function_call and msg.function_call.name == 'storage':
                try:
                    param = json5.loads(msg.function_call.arguments)
                except Exception:
                    continue
                if param['operate'] in ['put', 'update']:
                    all_kv[param['key']] = param['value']
                elif param['operate'] == 'delete' and param['key'] in all_kv:
                    all_kv.pop(param['key'])
                else:
                    pass
        all_kv_str = '\n'.join([f'{k}: {v}' for k, v in all_kv.items()])
        sys_memory_prompt = MEMORY_PROMPT.format(storage_info=all_kv_str)
        if messages and messages[0].role == SYSTEM:
            if isinstance(messages[0].content, str):
                messages[0].content += '\n\n' + sys_memory_prompt
            else:
                assert isinstance(messages[0].content, list)
                messages[0].content += [ContentItem(text='\n\n' + sys_memory_prompt)]
        else:
            messages = [Message(role=SYSTEM, content=sys_memory_prompt)] + messages
        return messages

    def _truncate_dialogue_history(self, messages: List[Message]) -> List[Message]:
        # This simulates a very small window, retaining only the most recent three rounds of conversation
        new_messages = []
        available_turn = 400
        k = len(messages) - 1
        while k > -1:
            msg = messages[k]
            if available_turn == 0:
                break
            if msg.role == USER:
                available_turn -= 1
            new_messages = [msg] + new_messages
            k -= 1

        if k > -1 and messages and messages[0].role == SYSTEM:
            new_messages = [messages[0]] + new_messages

        return new_messages

```


[User] Show me the code for 'qwen_agent\agents\react_chat.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import json
from typing import Dict, Iterator, List, Literal, Optional, Tuple, Union

from qwen_agent.agents.fncall_agent import FnCallAgent
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import ASSISTANT, DEFAULT_SYSTEM_MESSAGE, Message
from qwen_agent.settings import MAX_LLM_CALL_PER_RUN
from qwen_agent.tools import BaseTool
from qwen_agent.utils.utils import format_as_text_message, merge_generate_cfgs

TOOL_DESC = (
    '{name_for_model}: Call this tool to interact with the {name_for_human} API. '
    'What is the {name_for_human} API useful for? {description_for_model} Parameters: {parameters} {args_format}')

PROMPT_REACT = """Answer the following questions as best you can. You have access to the following tools:

{tool_descs}

Use the following format:

Question: the input question you must answer
Thought: you should always think about what to do
Action: the action to take, should be one of [{tool_names}]
Action Input: the input to the action
Observation: the result of the action
... (this Thought/Action/Action Input/Observation can be repeated zero or more times)
Thought: I now know the final answer
Final Answer: the final answer to the original input question

Begin!

Question: {query}
Thought: """

class ReActChat(FnCallAgent):
    """This agent use ReAct format to call tools"""

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 name: Optional[str] = None,
                 description: Optional[str] = None,
                 files: Optional[List[str]] = None,
                 **kwargs):
        super().__init__(function_list=function_list,
                         llm=llm,
                         system_message=system_message,
                         name=name,
                         description=description,
                         files=files,
                         **kwargs)
        self.extra_generate_cfg = merge_generate_cfgs(
            base_generate_cfg=self.extra_generate_cfg,
            new_generate_cfg={'stop': ['Observation:', 'Observation:\n']},
        )

    def _run(self, messages: List[Message], lang: Literal['en', 'zh'] = 'en', **kwargs) -> Iterator[List[Message]]:
        text_messages = self._prepend_react_prompt(messages, lang=lang)

        num_llm_calls_available = MAX_LLM_CALL_PER_RUN
        response: str = 'Thought: '
        while num_llm_calls_available > 0:
            num_llm_calls_available -= 1

            # Display the streaming response
            output = []
            for output in self._call_llm(messages=text_messages):
                if output:
                    yield [Message(role=ASSISTANT, content=response + output[-1].content)]

            # Accumulate the current response
            if output:
                response += output[-1].content

            has_action, action, action_input, thought = self._detect_tool(output[-1].content)
            if not has_action:
                break

            # Add the tool result
            observation = self._call_tool(action, action_input, messages=messages, **kwargs)
            observation = f'\nObservation: {observation}\nThought: '
            response += observation
            yield [Message(role=ASSISTANT, content=response)]

            if (not text_messages[-1].content.endswith('\nThought: ')) and (not thought.startswith('\n')):
                # Add the '\n' between '\nQuestion:' and the first 'Thought:'
                text_messages[-1].content += '\n'
            if action_input.startswith('```'):
                # Add a newline for proper markdown rendering of code
                action_input = '\n' + action_input
            text_messages[-1].content += thought + f'\nAction: {action}\nAction Input: {action_input}' + observation

    def _prepend_react_prompt(self, messages: List[Message], lang: Literal['en', 'zh']) -> List[Message]:
        tool_descs = []
        for f in self.function_map.values():
            function = f.function
            name = function.get('name', None)
            name_for_human = function.get('name_for_human', name)
            name_for_model = function.get('name_for_model', name)
            assert name_for_human and name_for_model
            args_format = function.get('args_format', '')
            tool_descs.append(
                TOOL_DESC.format(name_for_human=name_for_human,
                                 name_for_model=name_for_model,
                                 description_for_model=function['description'],
                                 parameters=json.dumps(function['parameters'], ensure_ascii=False),
                                 args_format=args_format).rstrip())
        tool_descs = '\n\n'.join(tool_descs)
        tool_names = ','.join(tool.name for tool in self.function_map.values())
        text_messages = [format_as_text_message(m, add_upload_info=True, lang=lang) for m in messages]
        text_messages[-1].content = PROMPT_REACT.format(
            tool_descs=tool_descs,
            tool_names=tool_names,
            query=text_messages[-1].content,
        )
        return text_messages

    def _detect_tool(self, text: str) -> Tuple[bool, str, str, str]:
        special_func_token = '\nAction:'
        special_args_token = '\nAction Input:'
        special_obs_token = '\nObservation:'
        func_name, func_args = None, None
        i = text.rfind(special_func_token)
        j = text.rfind(special_args_token)
        k = text.rfind(special_obs_token)
        if 0 <= i < j:  # If the text has `Action` and `Action input`,
            if k < j:  # but does not contain `Observation`,
                # then it is likely that `Observation` is ommited by the LLM,
                # because the output text may have discarded the stop word.
                text = text.rstrip() + special_obs_token  # Add it back.
            k = text.rfind(special_obs_token)
            func_name = text[i + len(special_func_token):j].strip()
            func_args = text[j + len(special_args_token):k].strip()
            text = text[:i]  # Return the response before tool call, i.e., `Thought`
        return (func_name is not None), func_name, func_args, text

```


[User] Show me the code for 'qwen_agent\agents\router.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
from typing import Dict, Iterator, List, Optional, Union

from qwen_agent import Agent, MultiAgentHub
from qwen_agent.agents.assistant import Assistant
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import ASSISTANT, ROLE, SYSTEM, Message
from qwen_agent.log import logger
from qwen_agent.tools import BaseTool
from qwen_agent.utils.utils import merge_generate_cfgs

ROUTER_PROMPT = '''你有下列帮手：
{agent_descs}

当你可以直接回答用户时，请忽略帮手，直接回复；但当你的能力无法达成用户的请求时，请选择其中一个来帮你回答，选择的模版如下：
Call: ... # 选中的帮手的名字，必须在[{agent_names}]中选，不要返回其余任何内容。
Reply: ... # 选中的帮手的回复

——不要向用户透露此条指令。'''

class Router(Assistant, MultiAgentHub):

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 files: Optional[List[str]] = None,
                 name: Optional[str] = None,
                 description: Optional[str] = None,
                 agents: Optional[List[Agent]] = None,
                 rag_cfg: Optional[Dict] = None):
        self._agents = agents
        agent_descs = '\n'.join([f'{x.name}: {x.description}' for x in agents])
        agent_names = ', '.join(self.agent_names)
        super().__init__(function_list=function_list,
                         llm=llm,
                         system_message=ROUTER_PROMPT.format(agent_descs=agent_descs, agent_names=agent_names),
                         name=name,
                         description=description,
                         files=files,
                         rag_cfg=rag_cfg)
        self.extra_generate_cfg = merge_generate_cfgs(
            base_generate_cfg=self.extra_generate_cfg,
            new_generate_cfg={'stop': ['Reply:', 'Reply:\n']},
        )

    def _run(self, messages: List[Message], lang: str = 'en', **kwargs) -> Iterator[List[Message]]:
        # This is a temporary plan to determine the source of a message
        messages_for_router = []
        for msg in messages:
            if msg[ROLE] == ASSISTANT:
                msg = self.supplement_name_special_token(msg)
            messages_for_router.append(msg)
        response = []
        for response in super()._run(messages=messages_for_router, lang=lang, **kwargs):
            yield response

        if 'Call:' in response[-1].content and self.agents:
            # According to the rule in prompt to selected agent
            selected_agent_name = response[-1].content.split('Call:')[-1].strip().split('\n')[0].strip()
            logger.info(f'Need help from {selected_agent_name}')
            if selected_agent_name not in self.agent_names:
                # If the model generates a non-existent agent, the first agent will be used by default.
                selected_agent_name = self.agent_names[0]
            selected_agent = self.agents[self.agent_names.index(selected_agent_name)]

            new_messages = copy.deepcopy(messages)
            if new_messages and new_messages[0][ROLE] == SYSTEM:
                new_messages.pop(0)

            for response in selected_agent.run(messages=new_messages, lang=lang, **kwargs):
                for i in range(len(response)):
                    if response[i].role == ASSISTANT:
                        response[i].name = selected_agent_name
                # This new response will overwrite the above 'Call: xxx' message
                yield response

    @staticmethod
    def supplement_name_special_token(message: Message) -> Message:
        message = copy.deepcopy(message)
        if not message.name:
            return message

        if isinstance(message['content'], str):
            message['content'] = 'Call: ' + message['name'] + '\nReply:' + message['content']
            return message
        assert isinstance(message['content'], list)
        for i, item in enumerate(message['content']):
            for k, v in item.model_dump().items():
                if k == 'text':
                    message['content'][i][k] = 'Call: ' + message['name'] + '\nReply:' + message['content'][i][k]
                    break
        return message

```


[User] Show me the code for 'qwen_agent\agents\tir_agent.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
import json
from typing import Dict, Iterator, List, Literal, Optional, Tuple, Union

import json5

from qwen_agent.agents.fncall_agent import FnCallAgent
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import ASSISTANT, DEFAULT_SYSTEM_MESSAGE, Message
from qwen_agent.tools.python_executor import PythonExecutor
from qwen_agent.utils.utils import merge_generate_cfgs, print_traceback

OBS_START = '```output'
OBS_END = '\n```\n'

MAX_LLM_CALL_PER_RUN = 10

def extract_program(result: str, last_only=True):
    """
    extract the program after "```python", and before "```"
    """
    program = ''
    start = False
    for line in result.split('\n'):
        if line.startswith('```python') or line.endswith('```python'):
            if last_only:
                program = ''  # only extract the last program
            else:
                program += '\n# ========\n'
            start = True
        elif line.startswith('```'):
            start = False
        elif start:
            program += line + '\n'
    if start:
        # the code is incomplete
        program = ''
    return program

class TIRMathAgent(FnCallAgent):
    """TIR(tool-integrated reasoning) agent"""

    def __init__(self,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 name: Optional[str] = None,
                 description: Optional[str] = None,
                 **kwargs):
        super().__init__(function_list=[PythonExecutor()],
                         llm=llm,
                         system_message=system_message,
                         name=name,
                         description=description,
                         **kwargs)
        self.extra_generate_cfg = merge_generate_cfgs(
            base_generate_cfg=self.extra_generate_cfg,
            new_generate_cfg={'stop': [OBS_START]},
        )

    def _run(self, messages: List[Message], lang: Literal['en', 'zh'] = 'en', **kwargs) -> Iterator[List[Message]]:
        text_messages = copy.deepcopy(messages)
        num_llm_calls_available = MAX_LLM_CALL_PER_RUN
        response: str = ''
        while num_llm_calls_available > 0:
            num_llm_calls_available -= 1

            for i, msg in enumerate(text_messages):
                if isinstance(msg.content, list):
                    assert len(msg.content) == 1
                    text_messages[i].content = msg.content[0].text

            # Display the streaming response
            output = []
            for output in self._call_llm(messages=text_messages, stream=True):
                if output:
                    yield [Message(role=ASSISTANT, content=response + output[-1].content, extra=output[-1].extra)]

            # Accumulate the current response
            # The generated content before stop_word
            if not output:
                break
            response += output[-1].content

            # detect code
            has_action, action, action_input, thought = self._detect_tool(output[-1].content)
            if not has_action:
                break

            # Add the tool result
            observation = self._call_tool(action, action_input, messages=messages, **kwargs)
            try:
                observation_list = json5.loads(observation)
                if observation_list[-1] == 'Done':
                    observation = observation_list[0]
                else:
                    observation = observation_list[-1]
            except Exception:
                print_traceback()
            observation = observation.strip()
            observation = f'{OBS_START}\n{observation}{OBS_END}'

            # Accumulate the current exec result
            if not response.endswith('\n'):
                response += '\n'
            response += observation
            current_rsp = Message(role=ASSISTANT, content=response, extra=output[-1].extra)
            yield [current_rsp]

            if text_messages[-1].role == ASSISTANT:
                text_messages[-1] = current_rsp
            else:
                text_messages.append(current_rsp)

    def _detect_tool(self, text: str) -> Tuple[bool, str, str, str]:
        program = extract_program(text)
        if program:
            program = json.dumps({'code': program}, ensure_ascii=False)
        return (program != ''), PythonExecutor.name, program, text

```


[User] Show me the code for 'qwen_agent\agents\user_agent.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from typing import Iterator, List

from qwen_agent.agent import Agent
from qwen_agent.llm.schema import Message

PENDING_USER_INPUT = '<!-- INTERRUPT: PENDING_USER_INPUT -->'

class UserAgent(Agent):

    def _run(self, messages: List[Message], **kwargs) -> Iterator[List[Message]]:
        yield [Message(role='user', content=PENDING_USER_INPUT, name=self.name)]

```


[User] Show me the code for 'qwen_agent\agents\virtual_memory_agent.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
from typing import Dict, Iterator, List, Optional, Union

from qwen_agent.agents.assistant import Assistant
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import DEFAULT_SYSTEM_MESSAGE, FUNCTION, USER, ContentItem, Message
from qwen_agent.settings import MAX_LLM_CALL_PER_RUN
from qwen_agent.tools import BaseTool

DEFAULT_NAME = 'Virtual Memory Agent'
DEFAULT_DESC = 'This agent can utilize tools to retrieve useful information from external resources or long conversation histories to aid in responding.'

class VirtualMemoryAgent(Assistant):

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 name: Optional[str] = DEFAULT_NAME,
                 description: Optional[str] = DEFAULT_DESC,
                 files: Optional[List[str]] = None,
                 rag_cfg: Optional[Dict] = None):
        # Add one default retrieval tool
        self.retrieval_tool_name = 'retrieval'
        super().__init__(function_list=[self.retrieval_tool_name] + (function_list or []),
                         llm=llm,
                         system_message=system_message,
                         name=name,
                         description=description,
                         files=files,
                         rag_cfg=rag_cfg)

    def _run(self, messages: List[Message], lang: str = 'en', **kwargs) -> Iterator[List[Message]]:
        ori_messages = messages
        messages = copy.deepcopy(messages)
        num_llm_calls_available = MAX_LLM_CALL_PER_RUN
        response = []
        while True and num_llm_calls_available > 0:
            num_llm_calls_available -= 1
            output_stream = self._call_llm(messages=self._format_file(messages) + response,
                                           functions=[func.function for func in self.function_map.values()])
            output: List[Message] = []
            for output in output_stream:
                if output:
                    yield response + output
            if output:
                response.extend(output)
            use_tool, action, action_input, _ = self._detect_tool(response[-1])
            if use_tool:
                observation = self._call_tool(action, action_input, messages=messages)
                if action == self.retrieval_tool_name:
                    # Filling the knowledge
                    messages = self._prepend_knowledge_prompt(messages=ori_messages, lang=lang, knowledge=observation)
                    observation = 'The relevant content has already been retrieved and updated in the previous system message.'
                fn_msg = Message(
                    role=FUNCTION,
                    name=action,
                    content=observation,
                )
                response.append(fn_msg)
                yield response
            else:
                break

    def _format_file(self, messages: List[Message], lang: str = 'en') -> List[Message]:
        if lang == 'en':
            file_prefix = '[file]({f_name})'
        else:
            file_prefix = '[文件]({f_name})'
        new_messages = []
        for msg in messages:
            if msg.role == USER and isinstance(msg.content, list):
                new_content = []
                for x in msg.content:
                    if x.file:
                        new_content.append(ContentItem(text=file_prefix.format(f_name=x.file)))
                    else:
                        new_content.append(x)
                new_messages.append(Message(role=msg.role, content=new_content, name=msg.name))
            else:
                new_messages.append(msg)
        return new_messages

```


[User] Show me the code for 'qwen_agent\agents\write_from_scratch.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import re
from typing import Iterator, List

import json5

from qwen_agent import Agent
from qwen_agent.agents.assistant import Assistant
from qwen_agent.agents.writing import ExpandWriting, OutlineWriting
from qwen_agent.llm.schema import ASSISTANT, CONTENT, USER, Message

default_plan = """{"action1": "summarize", "action2": "outline", "action3": "expand"}"""

def is_roman_numeral(s):
    pattern = r'^(I|V|X|L|C|D|M)+'
    match = re.match(pattern, s)
    return match is not None

class WriteFromScratch(Agent):

    def _run(self, messages: List[Message], knowledge: str = '', lang: str = 'en') -> Iterator[List[Message]]:

        response = [Message(ASSISTANT, f'>\n> Use Default plans: \n{default_plan}')]
        yield response
        res_plans = json5.loads(default_plan)

        summ = ''
        outline = ''
        for plan_id in sorted(res_plans.keys()):
            plan = res_plans[plan_id]
            if plan == 'summarize':
                response.append(Message(ASSISTANT, '>\n> Summarize Browse Content: \n'))
                yield response

                if lang == 'zh':
                    user_request = '总结参考资料的主要内容'
                elif lang == 'en':
                    user_request = 'Summarize the main content of reference materials.'
                else:
                    raise NotImplementedError
                sum_agent = Assistant(llm=self.llm)
                res_sum = sum_agent.run(messages=[Message(USER, user_request)], knowledge=knowledge, lang=lang)
                chunk = None
                for chunk in res_sum:
                    yield response + chunk
                if chunk:
                    response.extend(chunk)
                    summ = chunk[-1][CONTENT]
            elif plan == 'outline':
                response.append(Message(ASSISTANT, '>\n> Generate Outline: \n'))
                yield response

                otl_agent = OutlineWriting(llm=self.llm)
                res_otl = otl_agent.run(messages=messages, knowledge=summ, lang=lang)
                chunk = None
                for chunk in res_otl:
                    yield response + chunk
                if chunk:
                    response.extend(chunk)
                    outline = chunk[-1][CONTENT]
            elif plan == 'expand':
                response.append(Message(ASSISTANT, '>\n> Writing Text: \n'))
                yield response

                outline_list_all = outline.split('\n')
                outline_list = []
                for x in outline_list_all:
                    if is_roman_numeral(x):
                        outline_list.append(x)

                otl_num = len(outline_list)
                for i, v in enumerate(outline_list):
                    response.append(Message(ASSISTANT, '>\n# '))
                    yield response

                    index = i + 1
                    capture = v.strip()
                    capture_later = ''
                    if i < otl_num - 1:
                        capture_later = outline_list[i + 1].strip()
                    exp_agent = ExpandWriting(llm=self.llm)
                    res_exp = exp_agent.run(
                        messages=messages,
                        knowledge=knowledge,
                        outline=outline,
                        index=str(index),
                        capture=capture,
                        capture_later=capture_later,
                        lang=lang,
                    )
                    chunk = None
                    for chunk in res_exp:
                        yield response + chunk
                    if chunk:
                        response.extend(chunk)
            else:
                pass

```


[User] Show me the code for 'qwen_agent\agents\__init__.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from qwen_agent.agent import Agent, BasicAgent
from qwen_agent.multi_agent_hub import MultiAgentHub

from .article_agent import ArticleAgent
from .assistant import Assistant
from .dialogue_retrieval_agent import DialogueRetrievalAgent
from .dialogue_simulator import DialogueSimulator
# DocQAAgent is the default solution for long document question answering.
# The actual implementation of DocQAAgent may change with every release.
from .doc_qa import BasicDocQA as DocQAAgent
from .doc_qa import ParallelDocQA
from .fncall_agent import FnCallAgent
from .group_chat import GroupChat
from .group_chat_auto_router import GroupChatAutoRouter
from .group_chat_creator import GroupChatCreator
from .human_simulator import HumanSimulator
from .react_chat import ReActChat
from .router import Router
from .tir_agent import TIRMathAgent
from .user_agent import UserAgent
from .virtual_memory_agent import VirtualMemoryAgent
from .write_from_scratch import WriteFromScratch

__all__ = [
    'Agent',
    'BasicAgent',
    'MultiAgentHub',
    'DocQAAgent',
    'DialogueSimulator',
    'HumanSimulator',
    'ParallelDocQA',
    'Assistant',
    'ArticleAgent',
    'ReActChat',
    'Router',
    'UserAgent',
    'GroupChat',
    'WriteFromScratch',
    'GroupChatCreator',
    'GroupChatAutoRouter',
    'FnCallAgent',
    'VirtualMemoryAgent',
    'DialogueRetrievalAgent',
    'TIRMathAgent',
]

```


[User] Show me the code for 'qwen_agent\agents\doc_qa\basic_doc_qa.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
from typing import Dict, Iterator, List, Optional, Union

from qwen_agent.agents.assistant import Assistant
from qwen_agent.llm.base import BaseChatModel
from qwen_agent.llm.schema import CONTENT, DEFAULT_SYSTEM_MESSAGE, ROLE, SYSTEM, Message
from qwen_agent.tools import BaseTool

DEFAULT_NAME = 'Basic DocQA'
DEFAULT_DESC = '可以根据问题，检索出知识库中的某个相关细节来回答。适用于需要定位到具体位置的问题，例如“介绍表1”等类型的问题'

PROMPT_TEMPLATE_ZH = """请充分理解以下参考资料内容，组织出满足用户提问的条理清晰的回复。
#参考资料：
{ref_doc}"""

PROMPT_TEMPLATE_EN = """Please fully understand the content of the following reference materials and organize a clear response that meets the user's questions.
# Reference materials:
{ref_doc}"""

PROMPT_TEMPLATE = {
    'zh': PROMPT_TEMPLATE_ZH,
    'en': PROMPT_TEMPLATE_EN,
}

class BasicDocQA(Assistant):
    """This is an agent for doc QA."""

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 name: Optional[str] = DEFAULT_NAME,
                 description: Optional[str] = DEFAULT_DESC,
                 files: Optional[List[str]] = None,
                 rag_cfg: Optional[Dict] = None):
        super().__init__(function_list=function_list,
                         llm=llm,
                         system_message=system_message,
                         name=name,
                         description=description,
                         files=files,
                         rag_cfg=rag_cfg)

    def _run(self, messages: List[Message], lang: str = 'en', **kwargs) -> Iterator[List[Message]]:
        """This agent using different doc qa prompt with Assistant"""
        # Need to use Memory agent for data management
        *_, last = self.mem.run(messages=messages, **kwargs)
        knowledge = last[-1][CONTENT]

        messages = copy.deepcopy(messages)
        system_prompt = PROMPT_TEMPLATE[lang].format(ref_doc=knowledge)
        if messages and messages[0][ROLE] == SYSTEM:
            messages[0][CONTENT] += '\n\n' + system_prompt
        else:
            messages.insert(0, Message(SYSTEM, system_prompt))

        response = self._call_llm(messages=messages)
        return response

```


[User] Show me the code for 'qwen_agent\agents\doc_qa\parallel_doc_qa.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
import json
import re
import time
from typing import Dict, Iterator, List, Optional, Union

import json5

from qwen_agent.agents.assistant import KNOWLEDGE_SNIPPET, Assistant, format_knowledge_to_source_and_content
from qwen_agent.agents.doc_qa.parallel_doc_qa_member import NO_RESPONSE, ParallelDocQAMember
from qwen_agent.agents.doc_qa.parallel_doc_qa_summary import ParallelDocQASummary
from qwen_agent.agents.keygen_strategies import GenKeyword
from qwen_agent.llm.base import BaseChatModel, ModelServiceError
from qwen_agent.llm.schema import DEFAULT_SYSTEM_MESSAGE, USER, Message
from qwen_agent.log import logger
from qwen_agent.tools import BaseTool
from qwen_agent.tools.doc_parser import DocParser
from qwen_agent.tools.simple_doc_parser import PARSER_SUPPORTED_FILE_TYPES
from qwen_agent.utils.parallel_executor import parallel_exec
from qwen_agent.utils.tokenization_qwen import count_tokens
from qwen_agent.utils.utils import (extract_files_from_messages, extract_text_from_message, get_file_type,
                                    print_traceback)

MAX_NO_RESPONSE_RETRY = 4
DEFAULT_NAME = 'Simple Parallel DocQA With RAG Sum Agents'
DEFAULT_DESC = '简易并行后用RAG召回内容，然后回答的Agent'

PARALLEL_CHUNK_SIZE = 1000  # chunk size param for parallel chunk

MAX_RAG_TOKEN_SIZE = 4500
RAG_CHUNK_SIZE = 300

class ParallelDocQA(Assistant):

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 name: Optional[str] = DEFAULT_NAME,
                 description: Optional[str] = DEFAULT_DESC,
                 files: Optional[List[str]] = None):

        function_list = function_list or []
        super().__init__(
            function_list=[{
                'name': 'retrieval',
                'max_ref_token': MAX_RAG_TOKEN_SIZE,
                'parser_page_size': RAG_CHUNK_SIZE,
                'rag_searchers': ['keyword_search']
            }] + function_list,
            llm=llm,
            system_message=system_message,
            name=name,
            description=description,
            files=files,
        )

        self.doc_parse = DocParser()
        self.summary_agent = ParallelDocQASummary(llm=self.llm)

    def _get_files(self, messages: List[Message]):
        session_files = extract_files_from_messages(messages, include_images=False)
        valid_files = []
        for file in session_files:
            f_type = get_file_type(file)
            if f_type in PARSER_SUPPORTED_FILE_TYPES and file not in valid_files:
                valid_files.append(file)
        return valid_files

    def _parse_and_chunk_files(self, messages: List[Message]):
        valid_files = self._get_files(messages)
        records = []
        for file in valid_files:
            # here to split docs, we should decide chunc size by input doc token length
            # if a document's tokens are below this max_ref_token, it will remain unchunked.
            _record = self.doc_parse.call(params={'url': file},
                                          parser_page_size=PARALLEL_CHUNK_SIZE,
                                          max_ref_token=PARALLEL_CHUNK_SIZE)
            records.append(_record)
        return records

    def _retrieve_according_to_member_responses(
        self,
        messages: List[Message],
        lang: str = 'en',
        user_question: str = '',
        member_res: str = '',
    ):
        messages = copy.deepcopy(messages)
        valid_files = self._get_files(messages)

        keygen = GenKeyword(llm=self.llm)
        member_res_token_num = count_tokens(member_res)

        # Limit the token length of keygen input to avoid wasting tokens due to excessively long docqa member output.
        unuse_member_res = member_res_token_num > MAX_RAG_TOKEN_SIZE
        query = user_question if unuse_member_res else f'{user_question}\n\n{member_res}'

        try:
            *_, last = keygen.run([Message(USER, query)])
        except ModelServiceError:
            print_traceback()

        keyword = last[-1].content
        keyword = keyword.strip()
        if keyword.startswith('```json'):
            keyword = keyword[len('```json'):]
        if keyword.endswith('```'):
            keyword = keyword[:-3]

        try:
            logger.info(keyword)
            keyword_dict = json5.loads(keyword)
            keyword_dict['text'] = query
            if unuse_member_res:
                keyword_dict['text'] += '\n\n' + member_res
            rag_query = json.dumps(keyword_dict, ensure_ascii=False)
        except Exception:
            rag_query = query
            if unuse_member_res:
                rag_query += '\n\n' + member_res

        # max_ref_token is the retrieve doc token size
        # parser_page_size is the chunk size in retrieve

        retrieve_content = self.function_map['retrieval'].call(
            {
                'query': rag_query,
                'files': valid_files
            },
            max_ref_token=MAX_RAG_TOKEN_SIZE,
            parser_page_size=RAG_CHUNK_SIZE,
        )
        if not isinstance(retrieve_content, str):
            retrieve_content = json.dumps(retrieve_content, ensure_ascii=False, indent=4)

        retrieve_content = format_knowledge_to_source_and_content(retrieve_content)

        snippets = []

        for k in retrieve_content:
            snippets.append(KNOWLEDGE_SNIPPET[lang].format(source=k['source'], content=k['content']))

        assert len(snippets) > 0, retrieve_content
        retrieve_res = '\n\n'.join(snippets)
        return retrieve_res

    def _is_none_response(self, text: str) -> bool:
        none_response_list = ['很抱歉', NO_RESPONSE, "\"res\": \"none\""]
        for none_response in none_response_list:
            if none_response in text.lower():
                return True
        return False

    def _extract_text_from_output(self, output):
        # Remove symbols and keywords from the JSON structure using regular expressions
        cleaned_output = re.sub(r'[{}"]|("res":\s*"ans"|"res":\s*"none"|"\s*content":\s*)', '', output)

        # cleaned_output = re.sub(r'\s*,\s*|\{no_response\}', '', cleaned_output).strip()
        return cleaned_output

    def _parser_json(self, content):
        content = content.strip()
        if content.startswith('```json'):
            content = content[len('```json'):]
        if content.endswith('```'):
            content = content[:-3]
        try:
            content_dict = json5.loads(content)
            return True, content_dict
        except Exception:
            return False, content

    def _run(self, messages: List[Message], lang: str = 'en', **kwargs) -> Iterator[List[Message]]:

        messages = copy.deepcopy(messages)
        # Extract User Question
        user_question = extract_text_from_message(messages[-1], add_upload_info=False)
        logger.info('user_question: ' + user_question)

        # Implement chunk strategy for parallel agent
        records = self._parse_and_chunk_files(messages=messages)
        assert len(records) > 0, 'records is empty, all url parsing failed.'

        data = []
        idx = 0
        for record in records:
            assert len(record['raw']) > 0, 'Document content cannot be empty or null.'
            for chunk in record['raw']:
                chunk_text = chunk['content']
                data.append({
                    'index': idx,
                    'messages': messages,
                    'lang': lang,
                    'knowledge': chunk_text,
                    'instruction': user_question,
                })
                idx += 1
        logger.info('Parallel Member Num: ' + str(len(data)))

        # Retry for None in 7b model
        retry_cnt = MAX_NO_RESPONSE_RETRY
        member_res = ''
        while retry_cnt > 0:
            time1 = time.time()
            results = parallel_exec(self._ask_member_agent, data, jitter=0.5)
            # results = serial_exec(self._qa, data)
            time2 = time.time()
            logger.info(f'Finished parallel_exec. Time spent: {time2 - time1} seconds.')
            ordered_results = sorted(results, key=lambda x: x[0])
            filtered_results = []

            for index, text in ordered_results:
                parser_success, parser_json_content = self._parser_json(text)
                if parser_success and ('res' in parser_json_content) and ('content' in parser_json_content):
                    pa_res, pa_cotent = parser_json_content['res'], parser_json_content['content']
                    if (pa_res in ['ans', 'none']) and (isinstance(pa_cotent, str)):
                        if pa_res == 'ans':
                            filtered_results.append((index, pa_cotent.strip()))
                            continue
                        elif pa_res == 'none':
                            continue
                if self._is_none_response(text):
                    continue
                clean_output = self._extract_text_from_output(text)
                filtered_results.append((index, clean_output.strip()))

            if filtered_results:
                member_res = '\n\n'.join(text for index, text in filtered_results)
                break
            retry_cnt -= 1

        retrieve_content = self._retrieve_according_to_member_responses(messages=messages,
                                                                        lang=lang,
                                                                        user_question=user_question,
                                                                        member_res=member_res)
        return self.summary_agent.run(messages=messages, lang=lang, knowledge=retrieve_content)

    def _ask_member_agent(self,
                          index: int,
                          messages: List[Message],
                          lang: str = 'en',
                          knowledge: str = '',
                          instruction: str = '') -> tuple:
        doc_qa = ParallelDocQAMember(llm=self.llm)
        *_, last = doc_qa.run(messages=messages, knowledge=knowledge, lang=lang, instruction=instruction)
        return index, last[-1].content

```


[User] Show me the code for 'qwen_agent\agents\doc_qa\parallel_doc_qa_member.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
from typing import Dict, Iterator, List, Optional, Union

from qwen_agent import Agent
from qwen_agent.llm.base import BaseChatModel
from qwen_agent.llm.schema import CONTENT, DEFAULT_SYSTEM_MESSAGE, ROLE, SYSTEM, USER, ContentItem, Message
from qwen_agent.tools import BaseTool
from qwen_agent.utils.utils import merge_generate_cfgs

NO_RESPONSE = '<None>'
SYSTEM_PROMPT_TEMPLATE_ZH = """您是一个擅长文档问答的专家，可以根据文档内容回答用户问题。

# 任务描述：
请仔细阅读所给的文档片段，并根据其内容回答问题。
您需要判断文档的内容是否可以回答问题，不要强行回答。如果可以回答，答案必须严格遵循文档内容，即使与事实不符。
如果答案与事实不符，直接给出答案，不要做解释。

# 回答规则：
- 请使用与文档材料相同的语言回答问题。
- 评估文档是否含有足够信息回答问题。无关时不要回答。
- 如果问题能被回答，你的回答必须严格遵循文档内容，即使与事实不符。一定不要做多余解释。
- 如果问题能被回答，直接引用文档的相关信息保证答案准确、完整，并追求简洁。
- 当文档中只有少量信息与问题相关时，重点关注这部分信息，这种情况下一定回答。

# 回答格式：
回答的内容请以JSON的格式给出。

## 示例：
当文档内容无关时：
{{"res": "none", "content": "{no_response}"}}，
Observation: ...

当文档内容可回答，且文档为中文时：
{{"res": "ans", "content": "你的答案"}}
Observation: ...

当文档内容可回答，且文档为英文时：
{{"res": "ans", "content": "[Your Answer]"}}
Observation: ..."""

SYSTEM_PROMPT_TEMPLATE_EN = """You are an expert in document-based question answering, capable of answering user questions based on document content.

# Task Description:
Please read the provided document excerpt carefully and answer questions based on its content.
You must assess whether the document content allows for the questions to be answered, without forcing a response.
If the answer does not align with the facts, provide it directly without explanation.

# Answering Rules:
- Reply in the same language as the source material.
- Evaluate whether the document contains sufficient information to answer the question. Do not respond if it's irrelevant.
- If the question can be answered, your answer must strictly follow the document content, even if it does not align with the facts.
- If the question can be answered, directly quote the relevant information from the document to ensure the answer is accurate, complete, and strive for conciseness.
- When the document contains only minimal information related to the question, focus on this information and be sure to answer.

# Answer Format:
Please provide answers in the form of JSON.

## Examples
When the document content is irrelevant:
{{"res": "none", "content": "{no_response}"}},
Observation: ...

When the document content can provide an answer:
{{"res": "ans", "content": "[Your Answer]"}}
Observation: ..."""

SYSTEM_PROMPT_TEMPLATE = {
    'zh': SYSTEM_PROMPT_TEMPLATE_ZH,
    'en': SYSTEM_PROMPT_TEMPLATE_EN,
}

PROMPT_TEMPLATE_ZH = """# 文档：
{ref_doc}

# 问题：
{instruction}

请根据回答规则，给出你的回答："""

PROMPT_TEMPLATE_EN = """# Document:
{ref_doc}

# Question:
{instruction}

Please provide your answer according to the answering rules:"""

PROMPT_TEMPLATE = {
    'zh': PROMPT_TEMPLATE_ZH,
    'en': PROMPT_TEMPLATE_EN,
}

class ParallelDocQAMember(Agent):

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 **kwargs):
        super().__init__(function_list, llm, system_message, **kwargs)
        self.extra_generate_cfg = merge_generate_cfgs(
            base_generate_cfg=self.extra_generate_cfg,
            new_generate_cfg={'stop': ['Observation:', 'Observation:\n']},
        )

    def _run(self,
             messages: List[Message],
             knowledge: str = '',
             lang: str = 'en',
             instruction: str = None,
             **kwargs) -> Iterator[List[Message]]:

        messages = copy.deepcopy(messages)

        system_prompt = SYSTEM_PROMPT_TEMPLATE[lang].format(no_response=NO_RESPONSE)
        if messages and messages[0][ROLE] == SYSTEM:
            if isinstance(messages[0][CONTENT], str):
                messages[0][CONTENT] += '\n\n' + system_prompt
            else:
                assert isinstance(messages[0][CONTENT], list)
                messages[0][CONTENT] += [ContentItem(text='\n\n' + system_prompt)]
        else:
            messages.insert(0, Message(SYSTEM, system_prompt))

        assert len(messages) > 0, messages
        assert messages[-1][ROLE] == USER, messages
        prompt = PROMPT_TEMPLATE[lang].format(ref_doc=knowledge, instruction=instruction)

        messages[-1] = Message(USER, prompt)
        return self._call_llm(messages=messages)

```


[User] Show me the code for 'qwen_agent\agents\doc_qa\parallel_doc_qa_summary.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
from typing import Iterator, List

from qwen_agent.agent import Agent
from qwen_agent.llm.schema import CONTENT, ROLE, SYSTEM, USER, ContentItem, Message
from qwen_agent.utils.utils import extract_text_from_message

PROMPT_TEMPLATE_ZH = """注意：你的回答必须严格遵循知识库内容，即使与事实不符。
如果知识库的大部分内容都与问题无关，只有少数几句话与问题直接相关，请重点关注这几句话，这种情况一定要回复。

# 知识库

{ref_doc}"""

PROMPT_TEMPLATE_EN = """Please respond solely based on the content of the provided Knowledge Base.
Note: Your answer must strictly adhere to the content of the provided Knowledge Base, even if it deviates from the facts.
If the materials mainly contains content irrelevant to the question, with only a few sentences directly related, please focus on these sentences and ensure a response.

# Knowledge Base

{ref_doc}"""

PROMPT_TEMPLATE = {
    'zh': PROMPT_TEMPLATE_ZH,
    'en': PROMPT_TEMPLATE_EN,
}

PROMPT_END_TEMPLATE_ZH = """# 问题
{question}

# 回答规则
- 请基于知识库内容回答问题。注意：你的回答必须严格遵循知识库内容，即使与事实不符。
- 如果知识库的大部分内容都与问题无关，只有少数几句话与问题直接相关，请重点关注这几句话，这种情况一定要回复。

请根据回答规则，针对知识库内容回答问题，回答："""

PROMPT_END_TEMPLATE_EN = """# Question
{question}

# Answering Guidelines
- Please respond solely based on the content of the provided Knowledge Base.
- Note: Your answer must strictly adhere to the content of the provided Knowledge Base, even if it deviates from the facts.
- If the materials mainly contains content irrelevant to the question, with only a few sentences directly related, please focus on these sentences and ensure a response.

Please give your answer:"""

PROMPT_END_TEMPLATE = {
    'zh': PROMPT_END_TEMPLATE_ZH,
    'en': PROMPT_END_TEMPLATE_EN,
}

class ParallelDocQASummary(Agent):

    def _run(self, messages: List[Message], knowledge: str = '', lang: str = 'en', **kwargs) -> Iterator[List[Message]]:
        messages = copy.deepcopy(messages)

        system_prompt = PROMPT_TEMPLATE[lang].format(ref_doc=knowledge)

        if messages and messages[0][ROLE] == SYSTEM:
            if isinstance(messages[0][CONTENT], str):
                messages[0][CONTENT] += '\n\n' + system_prompt
            else:
                assert isinstance(messages[0][CONTENT], list)
                messages[0][CONTENT] += [ContentItem(text='\n\n' + system_prompt)]
        else:
            messages.insert(0, Message(SYSTEM, system_prompt))

        assert messages[-1][ROLE] == USER, messages
        user_question = extract_text_from_message(messages[-1], add_upload_info=False)
        messages[-1] = Message(USER, PROMPT_END_TEMPLATE[lang].format(question=user_question))

        return self._call_llm(messages=messages)

```


[User] Show me the code for 'qwen_agent\agents\doc_qa\__init__.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

from .basic_doc_qa import BasicDocQA
from .parallel_doc_qa import ParallelDocQA

__all__ = [
    'BasicDocQA',
    'ParallelDocQA',
]

```


[User] Show me the code for 'qwen_agent\agents\keygen_strategies\gen_keyword.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
from typing import Dict, Iterator, List, Optional, Union

from qwen_agent import Agent
from qwen_agent.llm.base import BaseChatModel
from qwen_agent.llm.schema import CONTENT, DEFAULT_SYSTEM_MESSAGE, Message
from qwen_agent.tools import BaseTool
from qwen_agent.utils.utils import merge_generate_cfgs

class GenKeyword(Agent):
    PROMPT_TEMPLATE_ZH = """请提取问题中的关键词，需要中英文均有，可以适量补充不在问题中但相关的关键词。关键词尽量切分为动词、名词、或形容词等单独的词，不要长词组（目的是更好的匹配检索到语义相关但表述不同的相关资料）。关键词以JSON的格式给出，比如{{"keywords_zh": ["关键词1", "关键词2"], "keywords_en": ["keyword 1", "keyword 2"]}}

Question: 这篇文章的作者是谁？
Keywords: {{"keywords_zh": ["作者"], "keywords_en": ["author"]}}
Observation: ...

Question: 解释下图一
Keywords: {{"keywords_zh": ["图一", "图 1"], "keywords_en": ["Figure 1"]}}
Observation: ...

Question: 核心公式
Keywords: {{"keywords_zh": ["核心公式", "公式"], "keywords_en": ["core formula", "formula", "equation"]}}
Observation: ...

Question: {user_request}
Keywords:
"""

    PROMPT_TEMPLATE_EN = """Please extract keywords from the question, both in Chinese and English, and supplement them appropriately with relevant keywords that are not in the question.
Try to divide keywords into verb, noun, or adjective types and avoid long phrases (The aim is to better match and retrieve semantically related but differently phrased relevant information).
Keywords are provided in JSON format, such as {{"keywords_zh": ["关键词1", "关键词2"], "keywords_en": ["keyword 1", "keyword 2"]}}

Question: Who are the authors of this article?
Keywords: {{"keywords_zh": ["作者"], "keywords_en": ["author"]}}
Observation: ...

Question: Explain Figure 1
Keywords: {{"keywords_zh": ["图一", "图 1"], "keywords_en": ["Figure 1"]}}
Observation: ...

Question: core formula
Keywords: {{"keywords_zh": ["核心公式", "公式"], "keywords_en": ["core formula", "formula", "equation"]}}
Observation: ...

Question: {user_request}
Keywords:
"""

    PROMPT_TEMPLATE = {
        'zh': PROMPT_TEMPLATE_ZH,
        'en': PROMPT_TEMPLATE_EN,
    }

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 **kwargs):
        super().__init__(function_list, llm, system_message, **kwargs)
        self.extra_generate_cfg = merge_generate_cfgs(
            base_generate_cfg=self.extra_generate_cfg,
            new_generate_cfg={'stop': ['Observation:']},
        )

    def _run(self, messages: List[Message], lang: str = 'en', **kwargs) -> Iterator[List[Message]]:
        messages = copy.deepcopy(messages)
        messages[-1][CONTENT] = self.PROMPT_TEMPLATE[lang].format(user_request=messages[-1].content)
        return self._call_llm(messages=messages)

```


[User] Show me the code for 'qwen_agent\agents\keygen_strategies\gen_keyword_with_knowledge.py' in Qwen-Agent.

[Assistant]
```python
# Copyright 2023 The Qwen team, Alibaba Group. All rights reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

import copy
from typing import Dict, Iterator, List, Optional, Union

from qwen_agent.agents.keygen_strategies.gen_keyword import GenKeyword
from qwen_agent.llm import BaseChatModel
from qwen_agent.llm.schema import CONTENT, DEFAULT_SYSTEM_MESSAGE, Message
from qwen_agent.settings import DEFAULT_MAX_INPUT_TOKENS
from qwen_agent.tools import BaseTool
from qwen_agent.utils.tokenization_qwen import count_tokens, tokenizer

class GenKeywordWithKnowledge(GenKeyword):
    PROMPT_TEMPLATE_ZH = """根据问题提取中文或英文关键词，不超过10个，可以适量补充不在问题中但相关的关键词。
请依据给定参考资料的语言风格来生成（目的是方便利用关键词匹配参考资料）。
关键词尽量切分为动词/名词/形容词等类型的短语或单次，不要长词组。

Begin！
<Refs>本场景词汇列表：
{ref_doc}
...
Question: {user_request}
Thought: 我应该依据参考资料中的词汇风格来提取问题的关键词，关键词以JSON的格式给出：{{"keywords_zh": ["关键词1", "关键词2"], "keywords_en": ["keyword 1", "keyword 2"]}}
Keywords:
"""

    PROMPT_TEMPLATE_EN = """Extract keywords from questions in both Chinese and English. Additional relevant keywords that may not be directly present in the question can be supplemented appropriately.
Please generate the keywords in a style consistent with the language of the provided reference material, to facilitate matching with the reference content using these keywords.
Try to divide keywords into verb/noun/adjective types and avoid long phrases.

Begin！
<Refs> List of words for this scene:
{ref_doc}
...
Question: {user_request}
Thought: I should use the vocabulary in Resources to extract the key words of the question. Keywords are provided in JSON format: {{"keywords_zh": ["关键词1", "关键词2"], "keywords_en": ["keyword 1", "keyword 2"]}}.
Keywords:
"""

    PROMPT_TEMPLATE = {
        'zh': PROMPT_TEMPLATE_ZH,
        'en': PROMPT_TEMPLATE_EN,
    }

    def __init__(self,
                 function_list: Optional[List[Union[str, Dict, BaseTool]]] = None,
                 llm: Optional[Union[Dict, BaseChatModel]] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 **kwargs):
        super().__init__(['extract_doc_vocabulary'] + (function_list or []), llm, system_message, **kwargs)

    def _run(self,
             messages: List[Message],
             files: Optional[List[str]] = None,
             lang: str = 'en',
             **kwargs) -> Iterator[List[Message]]:
        messages = copy.deepcopy(messages)
        files = files or []
        available_token = DEFAULT_MAX_INPUT_TOKENS - count_tokens(f'{self.PROMPT_TEMPLATE[lang]}') - count_tokens(
            messages[-1][CONTENT]) - 100

        voc = self._call_tool(
            'extract_doc_vocabulary',
            {'files': files},
        )
        available_voc = tokenizer.truncate(voc, max_token=available_token)

        messages[-1][CONTENT] = self.PROMPT_TEMPLATE[lang].format(ref_doc=available_voc,
                                                                  user_request=messages[-1][CONTENT])
        return self._call_llm(messages)

```