From 987eb6d38be5301f3c071c7f13fdd77b02cd095a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 23 Jan 2024 17:06:22 +0800 Subject: [PATCH 1/6] fix: now support parsing code in message.content when using tools_call. --- metagpt/provider/openai_api.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index 3358b3aad..dad44087c 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -31,6 +31,7 @@ from metagpt.provider.base_llm import BaseLLM from metagpt.provider.constant import GENERAL_FUNCTION_SCHEMA from metagpt.provider.llm_provider_registry import register_provider from metagpt.schema import Message +from metagpt.utils.common import CodeParser from metagpt.utils.cost_manager import Costs from metagpt.utils.exceptions import handle_exception from metagpt.utils.token_counter import ( @@ -247,6 +248,11 @@ class OpenAILLM(BaseLLM): ) return self._parse_arguments(message.tool_calls[0].function.arguments) elif message.tool_calls is None and message.content is not None: + # reponse is code, fix openai tools_call respond bug. + code_formats = ("```", '"""', "'''") + if message.content.startswith(code_formats) and message.content.endswith(code_formats): + code = CodeParser.parse_code(None, message.content) + return {"language": "python", "code": code} # reponse is message return {"language": "markdown", "code": self.get_choice_text(rsp)} else: From 31813f2512b176b1838c13a703cc640846362da6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 23 Jan 2024 17:16:15 +0800 Subject: [PATCH 2/6] add new test for tool_calls_rsp. --- tests/metagpt/provider/test_openai.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/metagpt/provider/test_openai.py b/tests/metagpt/provider/test_openai.py index 2e5799475..7a771bcac 100644 --- a/tests/metagpt/provider/test_openai.py +++ b/tests/metagpt/provider/test_openai.py @@ -87,6 +87,16 @@ class TestOpenAI: messages.append( ChatCompletionMessage(content="Completed a python code for hello world!", role="assistant", tool_calls=None) ) + # 添加 openai tool calls respond bug, code 出现在ChatCompletionMessage.content中 + messages.extend( + [ + ChatCompletionMessage(content="```python\nprint('hello world')```", role="assistant", tool_calls=None), + ChatCompletionMessage(content="'''python\nprint('hello world')'''", role="assistant", tool_calls=None), + ChatCompletionMessage( + content='"""python\nprint(\'hello world\')"""', role="assistant", tool_calls=None + ), + ] + ) choices = [ Choice(finish_reason="tool_calls", logprobs=None, index=i, message=msg) for i, msg in enumerate(messages) ] From a06d8023d640ac3e87853d4b51aed595c919fe05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 23 Jan 2024 17:36:19 +0800 Subject: [PATCH 3/6] update CodeParser.parse_code. --- metagpt/provider/openai_api.py | 2 +- metagpt/utils/common.py | 4 ++-- tests/metagpt/provider/test_openai.py | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index dad44087c..fc741f038 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -251,7 +251,7 @@ class OpenAILLM(BaseLLM): # reponse is code, fix openai tools_call respond bug. code_formats = ("```", '"""', "'''") if message.content.startswith(code_formats) and message.content.endswith(code_formats): - code = CodeParser.parse_code(None, message.content) + code = CodeParser.parse_code(None, message.content, start_ends=r'["\'`]{3}') return {"language": "python", "code": code} # reponse is message return {"language": "markdown", "code": self.get_choice_text(rsp)} diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py index b20b4acd2..36392debc 100644 --- a/metagpt/utils/common.py +++ b/metagpt/utils/common.py @@ -264,10 +264,10 @@ class CodeParser: return block_dict @classmethod - def parse_code(cls, block: str, text: str, lang: str = "") -> str: + def parse_code(cls, block: str, text: str, lang: str = "", start_ends: str = "```") -> str: if block: text = cls.parse_block(block, text) - pattern = rf"```{lang}.*?\s+(.*?)```" + pattern = rf"{start_ends}{lang}.*?\s+(.*?){start_ends}" match = re.search(pattern, text, re.DOTALL) if match: code = match.group(1) diff --git a/tests/metagpt/provider/test_openai.py b/tests/metagpt/provider/test_openai.py index 7a771bcac..1743fed92 100644 --- a/tests/metagpt/provider/test_openai.py +++ b/tests/metagpt/provider/test_openai.py @@ -139,6 +139,7 @@ class TestOpenAI: assert "code" in code assert "language" in code assert "hello world" in code["code"] + logger.info(f'code is : {code["code"]}') if "Completed a python code for hello world!" == code["code"]: code["language"] == "markdown" From cff4eff78d3d7b015f0cd49cd22e5dfe3276186d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 23 Jan 2024 17:46:41 +0800 Subject: [PATCH 4/6] update CodeParser.parse_code. --- metagpt/provider/openai_api.py | 2 +- metagpt/utils/common.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index fc741f038..dad44087c 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -251,7 +251,7 @@ class OpenAILLM(BaseLLM): # reponse is code, fix openai tools_call respond bug. code_formats = ("```", '"""', "'''") if message.content.startswith(code_formats) and message.content.endswith(code_formats): - code = CodeParser.parse_code(None, message.content, start_ends=r'["\'`]{3}') + code = CodeParser.parse_code(None, message.content) return {"language": "python", "code": code} # reponse is message return {"language": "markdown", "code": self.get_choice_text(rsp)} diff --git a/metagpt/utils/common.py b/metagpt/utils/common.py index 36392debc..ed73cb061 100644 --- a/metagpt/utils/common.py +++ b/metagpt/utils/common.py @@ -8,6 +8,7 @@ Add generic class-to-string and object-to-string conversion functionality. @Modified By: mashenquan, 2023/11/27. Bug fix: `parse_recipient` failed to parse the recipient in certain GPT-3.5 responses. +@Modified By: liubangbang, 2024/01/23. Update: support [```, ''', \"\"\" ] codes in CodeParser.parse_code. """ from __future__ import annotations @@ -264,7 +265,7 @@ class CodeParser: return block_dict @classmethod - def parse_code(cls, block: str, text: str, lang: str = "", start_ends: str = "```") -> str: + def parse_code(cls, block: str, text: str, lang: str = "", start_ends: str = r'["\'`]{3}') -> str: if block: text = cls.parse_block(block, text) pattern = rf"{start_ends}{lang}.*?\s+(.*?){start_ends}" From 0cc0a16e521d23d9d6fa5adee1120722b32a3e02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 23 Jan 2024 17:50:04 +0800 Subject: [PATCH 5/6] add new test for tool_calls_rsp. --- tests/metagpt/provider/test_openai.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tests/metagpt/provider/test_openai.py b/tests/metagpt/provider/test_openai.py index 1743fed92..77820a5f8 100644 --- a/tests/metagpt/provider/test_openai.py +++ b/tests/metagpt/provider/test_openai.py @@ -95,6 +95,10 @@ class TestOpenAI: ChatCompletionMessage( content='"""python\nprint(\'hello world\')"""', role="assistant", tool_calls=None ), + ChatCompletionMessage( + content="'''python\nprint(\"hello world\")'''", role="assistant", tool_calls=None + ), + ChatCompletionMessage(content="```python\nprint('hello world')```", role="assistant", tool_calls=None), ] ) choices = [ From bcda7ac951df9ede69b60ae3dccb30ad10203541 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=88=98=E6=A3=92=E6=A3=92?= Date: Tue, 23 Jan 2024 17:53:50 +0800 Subject: [PATCH 6/6] add comments for openai tools_call respond bug. --- metagpt/provider/openai_api.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index dad44087c..386c36c22 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -248,7 +248,8 @@ class OpenAILLM(BaseLLM): ) return self._parse_arguments(message.tool_calls[0].function.arguments) elif message.tool_calls is None and message.content is not None: - # reponse is code, fix openai tools_call respond bug. + # reponse is code, fix openai tools_call respond bug, + # The response content is `code``, but it appears in the content instead of the arguments. code_formats = ("```", '"""', "'''") if message.content.startswith(code_formats) and message.content.endswith(code_formats): code = CodeParser.parse_code(None, message.content)