QwenLM / Qwen-Agent

Agent framework and applications built upon Qwen2, featuring Function Calling, Code Interpreter, RAG, and Chrome extension.
https://pypi.org/project/qwen-agent/
Other
2.51k stars 249 forks source link

parallel_doc_qa_member的输出格式 #194

Closed chenk-gd closed 2 weeks ago

chenk-gd commented 2 weeks ago

发现一个挺奇怪的现象,当parallel_doc_qa_member的输入(knowledge)小于7000左右字符(不是token,没有计算对应多少token),输出的content符合格式要求(包含{‘res’: ‘xxx’, ‘content’:‘xxx’}),例如:

last=[Message({'role': 'assistant', 'content': '{"res": "ans", "content": "Queue Manager负责管理回忆存储和FIFO队列中的消息。当系统接收到新消息时,Queue Manager会将这些消息附加到FIFO队列中,拼接提示令牌并触发LLM推理以生成LLM输出(完成令牌)。同时,它将传入的消息和生成的LLM输出写入回忆存储(MemGPT消息数据库)。通过MemGPT函数调用检索回忆存储中的消息时,Queue Manager会将其附加到队列的末尾,以便重新插入到LLM的上下文窗口中。此外,Queue Manager还负责通过队列驱逐策略控制上下文溢出。当提示令牌超过底层LLM上下文窗口的‘警告令牌计数’(例如70%的上下文窗口)时,它会在队列中插入系统消息以警告LLM即将进行队列驱逐,允许LLM使用MemGPT函数将FIFO队列中的重要信息存储到工作上下文或存档存储。当提示令牌超过‘刷新令牌计数’(例如100%的上下文窗口)时,Queue Manager会清空队列以释放上下文窗口的空间:它驱逐特定数量的消息,生成一个新的递归摘要,并在队列被清空后立即查看这些信息。然而,它们永久存储在回忆存储中并通过MemGPT函数调用读取。"}'})]

但当输入超出7000+个字符时,输出不是json格式,没有‘res’内容:

last=[Message({'role': 'assistant', 'content': '在MemGPT中,队列管理器(Queue Manager)扮演着关键角色,负责处理消息的存储和检索。它接收新的消息,并将这些信息追加到先进先出(FIFO)队列中。当队列中的消息达到一定数量时,队列管理器会触发语言模型(LLM)进行推理,生成响应(即完成令牌)。同时,队列管理器还会将新收到的消息和由LLM产生的响应写入回忆存储(MemGPT消息数据库)。\n\n此外,队列管理器还负责监控上下文溢出的问题。当提示令牌的数量接近或达到LLM的上下文窗口限制时,它会向系统发送警告信息,提醒即将发生队列驱逐(即“内存压力”警告)。这促使LLM使用MemGPT的功能将FIFO队列中的关键信息存储到工作上下文或存档存储中。如果提示令牌的数量超过了上下文窗口的上限,队列管理器会执行队列刷新操作,移除一定数量的消息以释放空间,并生成一个新的递归摘要。\n\n被驱逐的消息虽然不再直接可见于LLM的当前上下文中,但它们仍然保存在回忆存储中,可以通过MemGPT的功能调用来访问。通过这种方式,队列管理器确保了系统能够有效地管理和利用有限的上下文窗口资源,同时保持对历史信息的可检索性。\n\n总的来说,队列管理器是MemGPT架构中的核心组件之一,它不仅负责消息的存储和检索,还承担着监控和控制上下文溢出的任务,从而保证系统的高效运行。'})]

返回的content就是一个字符串,不是要求的{‘res’:'xxx', 'content': 'xxx}格式。

这个是跟大模型的指令遵循能力有关? 但为什么跟输入的长度相关?我试过Qwen2-72B-Instruct和qwen2-7b-instruct都一样(用的ollama)

JialinWangPKU commented 2 weeks ago

您好,感谢关注我们的工作。 关于json格式的问题,请问有具体的例子可以复现吗? 我在本地测试的时候,测试将example整篇文章作为一个chunk输入,依然可以得到正确的json格式。

llm_72b_cfg = { 'model': "qwen2-72b-instruct", 'model_server': 'dashscope' }
bot = ParallelDocQA(llm=llm_72b_cfg)
    messages = [
        {
            'role': 'user',
            'content': [
                {
                    'text': '介绍一下Queue Manager'
                },
                {
                    'file': 'https://arxiv.org/pdf/2310.08560.pdf'
                },
            ]
        },
    ]
image
JialinWangPKU commented 2 weeks ago

关于json格式输出的问题,我们在qwen-agent的parallel_doc_qa Agent中也有做相关处理。

  1. 当json格式解析不成功时,会有兜底操作。具体逻辑可看 https://github.com/QwenLM/Qwen-Agent/blob/main/qwen_agent/agents/doc_qa/parallel_doc_qa.py#L203
  2. 关于指令遵循中json格式输出的问题,有不少解决方案,我们正在努力改进中
chenk-gd commented 2 weeks ago

谢谢回复。下面是代码,用的文章就是parallel_doc_qa.py用的MemGPT的论文。

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

from qwen_agent.agents import Assistant
from qwen_agent.agents.doc_qa.parallel_doc_qa_member import ParallelDocQAMember
from qwen_agent.llm.schema import DEFAULT_SYSTEM_MESSAGE, Message, ROLE, SYSTEM, CONTENT, USER
from qwen_agent.log import logger
from qwen_agent.tools import DocParser
from qwen_agent.tools.simple_doc_parser import PARSER_SUPPORTED_FILE_TYPES
from qwen_agent.utils.utils import extract_text_from_message, extract_files_from_messages, get_file_type

DEFAULT_NAME = 'Simple QA'
DEFAULT_DESC = '使用文章的所有内容作为输入,然后回答的Agent'
CHUNK_SIZE = 1024*64  # chunk size param for parallel chunk

llm_cfg = {
    'model': 'qwen2:72b-instruct',
    'api_key': 'None',
    'model_server': 'http://172.27.221.13:11434/v1',
    'api_base': 'http://172.27.221.13:11434/v1',
    'generate_cfg': {
        "max_input_tokens": 122880,
        "temperature": 0.2
    }
}

class MyAgent(Assistant):
    def __init__(self,
                 llm: Optional[Dict] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 name: Optional[str] = DEFAULT_NAME,
                 description: Optional[str] = DEFAULT_DESC,
                 files: Optional[List[str]] = None):
        super().__init__(
            function_list=[],
            llm=llm,
            system_message=system_message,
            name=name,
            description=description,
            files=files,
        )
        self.doc_parse = DocParser()

    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_files(self,  messages: List[Message]):
        files = self._get_files(messages)
        records = []
        for file in files:
            _record = self.doc_parse.call(params={'url': file},
                                          parser_page_size=CHUNK_SIZE,
                                          max_ref_token=CHUNK_SIZE)
            records.append(_record)

        contents = []
        for record in records:
            for r in record['raw']:
                contents.append(r['content'])

        return '\n'.join(contents)

    def _run(self, messages: List[Message], lang: str = 'en', **kwargs) -> Iterator[List[Message]]:
        print(f'{lang=}')
        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)

        knowledge = self._parse_files(messages)
        knowledge = knowledge[5000:12000]

        *_, last = self._ask_member_agent(index=0, messages=messages, lang=lang, knowledge=knowledge, instruction=user_question)

        return last   # 请忽略后续的错误,因为只是查看_ask_memeber_agent函数返回的内容。

    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)
        print(f'{last=}')
        return index, last[-1].content

def ask_question(question: str, file: str):
    time1 = time.time()
    bot = MyAgent(llm=llm_cfg)
    messages = [
        {
            'role': 'user',
            'content': [
                {
                    'text': f'{question}'
                },
                {
                    'file': f'{file}'
                },
            ]
        },
    ]
    rsp = None
    for rsp in bot.run(messages):
        # print('bot response:', rsp)
        pass
    print(rsp)
    time2 = time.time()
    logger.info(f'Finished Ask Question. Time spent: {time2 - time1} seconds.')

if __name__ == "__main__":
    file = 'resource/2310.08560v2.pdf'
    # question = "介绍实验方法"
    question = "介绍一下Queue Manager"
    ask_question(question, file)

在这一行 knowledge = knowledge[5000:12000], 如果限制knowledge长度在7000+以内(不是很准确的数字),就能返回要求的格式,但是超过这个长度就不行。

谢谢。

JialinWangPKU commented 2 weeks ago

您好,我本地并没有复现。

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

from qwen_agent.agents import Assistant
from qwen_agent.agents.doc_qa.parallel_doc_qa_member import ParallelDocQAMember
from qwen_agent.llm.schema import DEFAULT_SYSTEM_MESSAGE, Message, ROLE, SYSTEM, CONTENT, USER
from qwen_agent.log import logger
from qwen_agent.tools import DocParser
from qwen_agent.tools.simple_doc_parser import PARSER_SUPPORTED_FILE_TYPES
from qwen_agent.utils.utils import extract_text_from_message, extract_files_from_messages, get_file_type

DEFAULT_NAME = 'Simple QA'
DEFAULT_DESC = '使用文章的所有内容作为输入,然后回答的Agent'
CHUNK_SIZE = 1024*64  # chunk size param for parallel chunk

# llm_cfg = {
#     'model': 'qwen2:72b-instruct',
#     'api_key': 'None',
#     'model_server': 'http://172.27.221.13:11434/v1',
#     'api_base': 'http://172.27.221.13:11434/v1',
#     'generate_cfg': {
#         "max_input_tokens": 122880,
#         "temperature": 0.2
#     }
# }

qwen72b_llm = {'model': 'qwen2-72b-instruct', 'generate_cfg': {'max_retries': 10, "temperature": 0.2}}

class MyAgent(Assistant):
    def __init__(self,
                 llm: Optional[Dict] = None,
                 system_message: Optional[str] = DEFAULT_SYSTEM_MESSAGE,
                 name: Optional[str] = DEFAULT_NAME,
                 description: Optional[str] = DEFAULT_DESC,
                 files: Optional[List[str]] = None):
        super().__init__(
            function_list=[],
            llm=llm,
            system_message=system_message,
            name=name,
            description=description,
            files=files,
        )
        self.doc_parse = DocParser()

    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_files(self,  messages: List[Message]):
        files = self._get_files(messages)
        records = []
        for file in files:
            _record = self.doc_parse.call(params={'url': file},
                                          parser_page_size=CHUNK_SIZE,
                                          max_ref_token=CHUNK_SIZE)
            records.append(_record)

        contents = []
        for record in records:
            for r in record['raw']:
                contents.append(r['content'])

        return '\n'.join(contents)

    def _run(self, messages: List[Message], lang: str = 'en', **kwargs) -> Iterator[List[Message]]:
        print(f'{lang=}')
        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)

        knowledge = self._parse_files(messages)
        knowledge = knowledge[0:12000]

        *_, last = self._ask_member_agent(index=0, messages=messages, lang=lang, knowledge=knowledge, instruction=user_question)

        return last   # 请忽略后续的错误,因为只是查看_ask_memeber_agent函数返回的内容。

    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)
        print(f'{last=}')
        import pdb
        pdb.set_trace()
        return index, last[-1].content

def ask_question(question: str, file: str):
    time1 = time.time()
    bot = MyAgent(llm=qwen72b_llm)
    messages = [
        {
            'role': 'user',
            'content': [
                {
                    'text': f'{question}'
                },
                {
                    # 'file': f'{file}'
                    'file': 'https://arxiv.org/pdf/2310.08560.pdf'
                },
            ]
        },
    ]
    import pdb
    pdb.set_trace()
    rsp = None
    for rsp in bot.run(messages):
        # print('bot response:', rsp)
        pass
    print(rsp)
    time2 = time.time()
    logger.info(f'Finished Ask Question. Time spent: {time2 - time1} seconds.')

if __name__ == "__main__":
    file = 'resource/2310.08560v2.pdf'
    # question = "介绍实验方法"
    question = "介绍一下Queue Manager"
    ask_question(question, file)

设置 knowledge = knowledge[0:12000]或者 knowledge = knowledge[5000:12000] 返回的依然是有效的json格式。

image

您的代码是本地部署的,方便讲一下配置吗? 另外我们推荐参数使用默认配置,例如Temperature的设置建议为0.8或者0.7。

chenk-gd commented 2 weeks ago

哦,是我没说清楚,你把5000:12000 改成 5000:15000试试。 这个在7000范围内,是没问题的。 大模型是本地部署,用的ollama, ollama pull qwen2:72b-instruct. 把temperature改成0.8, knowledge[:12000], 依然是有问题。 ![Uploading 1718153233412.png…]()

chenk-gd commented 2 weeks ago

确认了应该跟ollama下载的qwen2-72b-instruct模型有关。换成官方的Qwen2-72B-Instruct-GPTQ-Int4就没问题了。 谢谢。