Merge pull request #736 from garylin2099/code_intepreter

Integrate CodeIntepreter
This commit is contained in:
geekan 2024-02-05 20:46:37 +08:00 committed by GitHub
commit 23c27627ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
61 changed files with 5076 additions and 84 deletions

View file

@ -167,4 +167,12 @@ class BaseLLM(ABC):
:return dict: return the first function arguments of choice, for example,
{'language': 'python', 'code': "print('Hello, World!')"}
"""
return json.loads(self.get_choice_function(rsp)["arguments"])
return json.loads(self.get_choice_function(rsp)["arguments"], strict=False)
def messages_to_prompt(self, messages: list[dict]):
"""[{"role": "user", "content": msg}] to user: <msg> etc."""
return "\n".join([f"{i['role']}: {i['content']}" for i in messages])
def messages_to_dict(self, messages):
"""objects to [{"role": "user", "content": msg}] etc."""
return [i.to_dict() for i in messages]

View file

@ -25,10 +25,10 @@ from tenacity import (
from metagpt.configs.llm_config import LLMConfig, LLMType
from metagpt.logs import log_llm_stream, logger
from metagpt.provider.base_llm import BaseLLM
from metagpt.provider.constant import GENERAL_FUNCTION_SCHEMA, GENERAL_TOOL_CHOICE
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 decode_image
from metagpt.utils.common import CodeParser, decode_image
from metagpt.utils.cost_manager import CostManager, Costs
from metagpt.utils.exceptions import handle_exception
from metagpt.utils.token_counter import (
@ -147,37 +147,41 @@ class OpenAILLM(BaseLLM):
def _func_configs(self, messages: list[dict], timeout=3, **kwargs) -> dict:
"""Note: Keep kwargs consistent with https://platform.openai.com/docs/api-reference/chat/create"""
if "tools" not in kwargs:
configs = {
"tools": [{"type": "function", "function": GENERAL_FUNCTION_SCHEMA}],
"tool_choice": GENERAL_TOOL_CHOICE,
}
configs = {"tools": [{"type": "function", "function": GENERAL_FUNCTION_SCHEMA}]}
kwargs.update(configs)
return self._cons_kwargs(messages=messages, timeout=timeout, **kwargs)
def _process_message(self, messages: Union[str, Message, list[dict], list[Message], list[str]]) -> list[dict]:
"""convert messages to list[dict]."""
# 全部转成list
if not isinstance(messages, list):
messages = [messages]
# 转成list[dict]
processed_messages = []
for msg in messages:
if isinstance(msg, str):
processed_messages.append({"role": "user", "content": msg})
elif isinstance(msg, dict):
assert set(msg.keys()) == set(["role", "content"])
processed_messages.append(msg)
elif isinstance(msg, Message):
processed_messages.append(msg.to_dict())
else:
raise ValueError(
f"Only support message type are: str, Message, dict, but got {type(messages).__name__}!"
)
return processed_messages
async def _achat_completion_function(self, messages: list[dict], timeout=3, **chat_configs) -> ChatCompletion:
messages = self._process_message(messages)
kwargs = self._func_configs(messages=messages, timeout=timeout, **chat_configs)
rsp: ChatCompletion = await self.aclient.chat.completions.create(**kwargs)
self._update_costs(rsp.usage)
return rsp
def _process_message(self, messages: Union[str, Message, list[dict], list[Message], list[str]]) -> list[dict]:
"""convert messages to list[dict]."""
if isinstance(messages, list):
messages = [Message(content=msg) if isinstance(msg, str) else msg for msg in messages]
return [msg if isinstance(msg, dict) else msg.to_dict() for msg in messages]
if isinstance(messages, Message):
messages = [messages.to_dict()]
elif isinstance(messages, str):
messages = [{"role": "user", "content": messages}]
else:
raise ValueError(
f"Only support messages type are: str, Message, list[dict], but got {type(messages).__name__}!"
)
return messages
async def aask_code(self, messages: Union[str, Message, list[dict]], **kwargs) -> dict:
async def aask_code(self, messages: list[dict], **kwargs) -> dict:
"""Use function of tools to ask a code.
Note: Keep kwargs consistent with https://platform.openai.com/docs/api-reference/chat/create
@ -187,18 +191,37 @@ class OpenAILLM(BaseLLM):
>>> rsp = await llm.aask_code(msg)
# -> {'language': 'python', 'code': "print('Hello, World!')"}
"""
messages = self._process_message(messages)
rsp = await self._achat_completion_function(messages, **kwargs)
return self.get_choice_function_arguments(rsp)
@handle_exception
# @handle_exception
def get_choice_function_arguments(self, rsp: ChatCompletion) -> dict:
"""Required to provide the first function arguments of choice.
:param dict rsp: same as in self.get_choice_function(rsp)
:return dict: return the first function arguments of choice, for example,
{'language': 'python', 'code': "print('Hello, World!')"}
"""
return json.loads(rsp.choices[0].message.tool_calls[0].function.arguments)
message = rsp.choices[0].message
if (
message.tool_calls is not None
and message.tool_calls[0].function is not None
and message.tool_calls[0].function.arguments is not None
):
# reponse is code
return json.loads(message.tool_calls[0].function.arguments, strict=False)
elif message.tool_calls is None and message.content is not None:
# 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)
return {"language": "python", "code": code}
# reponse is message
return {"language": "markdown", "code": self.get_choice_text(rsp)}
else:
logger.error(f"Failed to parse \n {rsp}\n")
raise Exception(f"Failed to parse \n {rsp}\n")
def get_choice_text(self, rsp: ChatCompletion) -> str:
"""Required to provide the first text of choice"""