diff --git a/metagpt/actions/action_node.py b/metagpt/actions/action_node.py index 07638ce423..ad3f0a1d18 100644 --- a/metagpt/actions/action_node.py +++ b/metagpt/actions/action_node.py @@ -237,12 +237,19 @@ def create_model_class(cls, class_name: str, mapping: Dict[str, Tuple[Type, Any] """基于pydantic v2的模型动态生成,用来检验结果类型正确性""" def check_fields(cls, values): - required_fields = set(mapping.keys()) + all_fields = set(mapping.keys()) + required_fields = set() + for k, v in mapping.items(): + type_v, field_info = v + if ActionNode.is_optional_type(type_v): + continue + required_fields.add(k) + missing_fields = required_fields - set(values.keys()) if missing_fields: raise ValueError(f"Missing fields: {missing_fields}") - unrecognized_fields = set(values.keys()) - required_fields + unrecognized_fields = set(values.keys()) - all_fields if unrecognized_fields: logger.warning(f"Unrecognized fields: {unrecognized_fields}") return values @@ -717,3 +724,12 @@ def from_pydantic(cls, model: Type[BaseModel], key: str = None): root_node.add_child(child_node) return root_node + + @staticmethod + def is_optional_type(tp) -> bool: + """Return True if `tp` is `typing.Optional[...]`""" + if typing.get_origin(tp) is Union: + args = typing.get_args(tp) + non_none_types = [arg for arg in args if arg is not type(None)] + return len(non_none_types) == 1 and len(args) == 2 + return False diff --git a/metagpt/actions/project_management_an.py b/metagpt/actions/project_management_an.py index 308579cc30..f530624337 100644 --- a/metagpt/actions/project_management_an.py +++ b/metagpt/actions/project_management_an.py @@ -12,7 +12,7 @@ REQUIRED_PACKAGES = ActionNode( key="Required packages", expected_type=Optional[List[str]], - instruction="Provide required packages in requirements.txt format.", + instruction="Provide required third-party packages in requirements.txt format.", example=["flask==1.1.2", "bcrypt==3.2.0"], ) diff --git a/metagpt/actions/write_code_an_draft.py b/metagpt/actions/write_code_an_draft.py index ed6c66cf6b..20ed201a36 100644 --- a/metagpt/actions/write_code_an_draft.py +++ b/metagpt/actions/write_code_an_draft.py @@ -139,7 +139,7 @@ class Main {\ end", "Anything UNCLEAR": "目前项目要求明确,没有不清楚的地方。"} ## Tasks -{"Required packages": ["无需Python包"], "Required Other language third-party packages": ["vue.js"], "Logic Analysis": [["index.html", "作为游戏的入口文件和主要的HTML结构"], ["styles.css", "包含所有的CSS样式,确保游戏界面美观"], ["main.js", "包含Main类,负责初始化游戏和绑定事件"], ["game.js", "包含Game类,负责游戏逻辑,如开始游戏、移动方块等"], ["storage.js", "包含Storage类,用于获取和设置玩家的最高分"]], "Task list": ["index.html", "styles.css", "storage.js", "game.js", "main.js"], "Full API spec": "", "Shared Knowledge": "\'game.js\' 包含游戏逻辑相关的函数,被 \'main.js\' 调用。", "Anything UNCLEAR": "目前项目要求明确,没有不清楚的地方。"} +{"Required packages": ["无需第三方包"], "Required Other language third-party packages": ["vue.js"], "Logic Analysis": [["index.html", "作为游戏的入口文件和主要的HTML结构"], ["styles.css", "包含所有的CSS样式,确保游戏界面美观"], ["main.js", "包含Main类,负责初始化游戏和绑定事件"], ["game.js", "包含Game类,负责游戏逻辑,如开始游戏、移动方块等"], ["storage.js", "包含Storage类,用于获取和设置玩家的最高分"]], "Task list": ["index.html", "styles.css", "storage.js", "game.js", "main.js"], "Full API spec": "", "Shared Knowledge": "\'game.js\' 包含游戏逻辑相关的函数,被 \'main.js\' 调用。", "Anything UNCLEAR": "目前项目要求明确,没有不清楚的地方。"} ## Code Files ----- index.html diff --git a/metagpt/utils/token_counter.py b/metagpt/utils/token_counter.py index 8e674f5751..a7df27258a 100644 --- a/metagpt/utils/token_counter.py +++ b/metagpt/utils/token_counter.py @@ -40,6 +40,7 @@ "gpt-4-vision-preview": {"prompt": 0.01, "completion": 0.03}, # TODO add extra image price calculator "gpt-4-1106-vision-preview": {"prompt": 0.01, "completion": 0.03}, "gpt-4o": {"prompt": 0.005, "completion": 0.015}, + "gpt-4o-mini": {"prompt": 0.00015, "completion": 0.0006}, "gpt-4o-2024-05-13": {"prompt": 0.005, "completion": 0.015}, "text-embedding-ada-002": {"prompt": 0.0004, "completion": 0.0}, "glm-3-turbo": {"prompt": 0.0007, "completion": 0.0007}, # 128k version, prompt + completion tokens=0.005¥/k-tokens @@ -210,6 +211,7 @@ "gpt-4-0613": 8192, "gpt-4-32k": 32768, "gpt-4-32k-0613": 32768, + "gpt-4o-mini": 128000, "gpt-3.5-turbo-0125": 16385, "gpt-3.5-turbo": 16385, "gpt-3.5-turbo-1106": 16385, @@ -355,8 +357,9 @@ def count_input_tokens(messages, model="gpt-3.5-turbo-0125"): "gpt-4-turbo", "gpt-4-vision-preview", "gpt-4-1106-vision-preview", - "gpt-4o-2024-05-13", "gpt-4o", + "gpt-4o-2024-05-13", + "gpt-4o-mini", }: tokens_per_message = 3 # # every reply is primed with <|start|>assistant<|message|> tokens_per_name = 1 diff --git a/requirements.txt b/requirements.txt index e74cfef6bb..db0204862e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -73,6 +73,8 @@ dashscope~=1.19.3 rank-bm25==0.2.2 # for tool recommendation jieba==0.42.1 # for tool recommendation volcengine-python-sdk[ark]~=1.0.94 +# llama-index-vector-stores-elasticsearch~=0.2.5 # Used by `metagpt/memory/longterm_memory.py` +# llama-index-vector-stores-chroma~=0.1.10 # Used by `metagpt/memory/longterm_memory.py` gymnasium==0.29.1 boto3~=1.34.69 spark_ai_python~=0.3.30 diff --git a/tests/metagpt/actions/test_action_node.py b/tests/metagpt/actions/test_action_node.py index 989e2249cb..58a6dd5179 100644 --- a/tests/metagpt/actions/test_action_node.py +++ b/tests/metagpt/actions/test_action_node.py @@ -6,7 +6,7 @@ @File : test_action_node.py """ from pathlib import Path -from typing import List, Tuple +from typing import List, Optional, Tuple import pytest from pydantic import BaseModel, Field, ValidationError @@ -302,6 +302,19 @@ def test_action_node_from_pydantic_and_print_everything(): assert "tasks" in code, "tasks should be in code" +def test_optional(): + mapping = { + "Logic Analysis": (Optional[List[Tuple[str, str]]], Field(default=None)), + "Task list": (Optional[List[str]], None), + "Plan": (Optional[str], ""), + "Anything UNCLEAR": (Optional[str], None), + } + m = {"Anything UNCLEAR": "a"} + t = ActionNode.create_model_class("test_class_1", mapping) + + t1 = t(**m) + assert t1 + + if __name__ == "__main__": - test_create_model_class() - test_create_model_class_with_mapping() + pytest.main([__file__, "-s"]) diff --git a/tests/metagpt/test_config.py b/tests/metagpt/test_config.py index 7ce5765cff..797daf5dc4 100644 --- a/tests/metagpt/test_config.py +++ b/tests/metagpt/test_config.py @@ -14,8 +14,8 @@ def test_config_1(): cfg = Config.default() llm = cfg.get_openai_llm() - assert llm is not None - assert llm.api_type == LLMType.OPENAI + if cfg.llm.api_type == LLMType.OPENAI: + assert llm is not None def test_config_from_dict(): diff --git a/tests/metagpt/test_context.py b/tests/metagpt/test_context.py index f8218c44dd..a6daf95cd2 100644 --- a/tests/metagpt/test_context.py +++ b/tests/metagpt/test_context.py @@ -53,8 +53,8 @@ def test_context_1(): def test_context_2(): ctx = Context() llm = ctx.config.get_openai_llm() - assert llm is not None - assert llm.api_type == LLMType.OPENAI + if ctx.config.llm.api_type == LLMType.OPENAI: + assert llm is not None kwargs = ctx.kwargs assert kwargs is not None