From 7bf72cca39cdc2f85ccbc3e694e3acdcd0e0ff26 Mon Sep 17 00:00:00 2001 From: "panxuchen.pxc" Date: Sun, 4 Feb 2024 15:33:30 +0800 Subject: [PATCH] refactor model configs --- README.md | 22 ++-- docs/sphinx_doc/source/agentscope.configs.rst | 11 -- docs/sphinx_doc/source/agentscope.models.rst | 10 +- docs/sphinx_doc/source/index.rst | 1 - .../sphinx_doc/source/tutorial/103-example.md | 20 +-- .../sphinx_doc/source/tutorial/104-usecase.md | 13 +- docs/sphinx_doc/source/tutorial/201-agent.md | 6 +- docs/sphinx_doc/source/tutorial/203-model.md | 46 +++---- examples/conversation/conversation.py | 11 +- .../configs/debate_agent_configs.json | 6 +- .../distributed/configs/model_configs.json | 24 ++-- examples/distributed/distributed_dialog.py | 4 +- examples/werewolf/README.md | 4 +- examples/werewolf/configs/agent_configs.json | 12 +- examples/werewolf/configs/model_configs.json | 17 ++- scripts/README.md | 78 +++++------ scripts/faschat/model_config.json | 6 +- scripts/flask_modelscope/model_config.json | 4 +- scripts/flask_transformers/model_config.json | 4 +- scripts/vllm_script/model_config.json | 8 +- src/agentscope/_init.py | 8 +- src/agentscope/agents/agent.py | 19 +-- src/agentscope/agents/dialog_agent.py | 22 ++-- src/agentscope/agents/dict_dialog_agent.py | 22 ++-- src/agentscope/configs/__init__.py | 0 src/agentscope/configs/model_config.py | 122 ------------------ src/agentscope/memory/temporary_memory.py | 4 +- src/agentscope/models/__init__.py | 78 ++++------- src/agentscope/models/config.py | 59 +++++++++ src/agentscope/models/model.py | 114 ++++++++-------- src/agentscope/models/openai_model.py | 54 ++++---- src/agentscope/models/post_model.py | 51 +++----- .../service/text_processing/summarization.py | 2 +- tests/prompt_engine_test.py | 21 ++- tests/record_api_invocation_test.py | 2 +- 35 files changed, 396 insertions(+), 489 deletions(-) delete mode 100644 docs/sphinx_doc/source/agentscope.configs.rst delete mode 100644 src/agentscope/configs/__init__.py delete mode 100644 src/agentscope/configs/model_config.py create mode 100644 src/agentscope/models/config.py diff --git a/README.md b/README.md index 33d3d1446..4007c9a94 100644 --- a/README.md +++ b/README.md @@ -97,12 +97,12 @@ AgentScope supports the following model API services: - [HuggingFace](https://huggingface.co/docs/api-inference/index) and [ModelScope](https://www.modelscope.cn/docs/%E9%AD%94%E6%90%ADv1.5%E7%89%88%E6%9C%AC%20Release%20Note%20(20230428)) inference APIs - Customized model APIs -| | Type Argument | Support APIs | -|----------------------|--------------------|---------------------------------------------------------------| -| OpenAI Chat API | `openai` | Standard OpenAI Chat API, FastChat and vllm | -| OpenAI DALL-E API | `openai_dall_e` | Standard DALL-E API | -| OpenAI Embedding API | `openai_embedding` | OpenAI embedding API | -| Post API | `post_api` | Huggingface/ModelScope inference API, and customized post API | +| | Model Type Argument | Support APIs | +|----------------------|---------------------|---------------------------------------------------------------| +| OpenAI Chat API | `openai` | Standard OpenAI Chat API, FastChat and vllm | +| OpenAI DALL-E API | `openai_dall_e` | Standard DALL-E API | +| OpenAI Embedding API | `openai_embedding` | OpenAI embedding API | +| Post API | `post_api` | Huggingface/ModelScope inference API, and customized post API | ##### OpenAI API Config @@ -110,9 +110,9 @@ For OpenAI APIs, you need to prepare a dict of model config with the following f ``` { - "type": "openai" | "openai_dall_e" | "openai_embedding", - "name": "{your_config_name}", # The name used to identify your config - "model_name": "{model_name, e.g. gpt-4}", # The used model in openai API + "model_id": "{your_config_name}", # The name used to identify the generated model + "model_type": "openai" | "openai_dall_e" | "openai_embedding", + "model": "{model name, e.g. gpt-4}", # The used model in openai API # Optional "api_key": "xxx", # The API key for OpenAI API. If not set, env @@ -128,8 +128,8 @@ For post requests APIs, the config contains the following fields. ``` { - "type": "post_api", - "name": "{your_config_name}", # The name used to identify config + "model_id": "{your_model_id}", # The name used to identify your model + "model_type": "post_api", "api_url": "https://xxx", # The target url "headers": { # Required headers ... diff --git a/docs/sphinx_doc/source/agentscope.configs.rst b/docs/sphinx_doc/source/agentscope.configs.rst deleted file mode 100644 index 8e5d96d96..000000000 --- a/docs/sphinx_doc/source/agentscope.configs.rst +++ /dev/null @@ -1,11 +0,0 @@ -Configs package -========================== - -model\_config module ---------------------------------------- - -.. automodule:: agentscope.configs.model_config - :members: - :undoc-members: - :show-inheritance: - diff --git a/docs/sphinx_doc/source/agentscope.models.rst b/docs/sphinx_doc/source/agentscope.models.rst index e2f52c50f..9e8cd7205 100644 --- a/docs/sphinx_doc/source/agentscope.models.rst +++ b/docs/sphinx_doc/source/agentscope.models.rst @@ -1,6 +1,14 @@ Models package ========================== +config module +------------------------------- + +.. automodule:: agentscope.models.config + :members: + :undoc-members: + :show-inheritance: + model module ------------------------------- @@ -29,6 +37,6 @@ Module contents --------------- .. automodule:: agentscope.models - :members: load_model_by_name, clear_model_configs, read_model_configs + :members: load_model_by_id, clear_model_configs, read_model_configs :undoc-members: :show-inheritance: diff --git a/docs/sphinx_doc/source/index.rst b/docs/sphinx_doc/source/index.rst index 8f32751d5..611e16542 100644 --- a/docs/sphinx_doc/source/index.rst +++ b/docs/sphinx_doc/source/index.rst @@ -29,7 +29,6 @@ AgentScope Documentation :caption: AgentScope API Reference agentscope.agents - agentscope.configs agentscope.memory agentscope.models agentscope.pipelines diff --git a/docs/sphinx_doc/source/tutorial/103-example.md b/docs/sphinx_doc/source/tutorial/103-example.md index e81fa29c2..58d47aee7 100644 --- a/docs/sphinx_doc/source/tutorial/103-example.md +++ b/docs/sphinx_doc/source/tutorial/103-example.md @@ -8,20 +8,20 @@ AgentScope is a versatile platform for building and running multi-agent applicat Agent is the basic composition and communication unit in AgentScope. To initialize a model-based agent, you need to prepare your configs for avaliable models. AgentScope supports a variety of APIs for pre-trained models. Here is a table outlining the supported APIs and the type of arguments required for each: -| Model Usage | Type Argument in AgentScope | Supported APIs | -| -------------------- | ------------------ |-----------------------------------------------------------------------------| -| Text generation | `openai` | Standard *OpenAI* chat API, FastChat and vllm | -| Image generation | `openai_dall_e` | *DALL-E* API for generating images | -| Embedding | `openai_embedding` | API for text embeddings | -| General usages in POST | `post_api` | *Huggingface* and *ModelScope* Inference API, and other customized post API | +| Model Usage | Model Type Argument in AgentScope | Supported APIs | +| --------------------------- | --------------------------------- |-----------------------------------------------------------------------------| +| Text generation | `openai` | Standard *OpenAI* chat API, FastChat and vllm | +| Image generation | `openai_dall_e` | *DALL-E* API for generating images | +| Embedding | `openai_embedding` | API for text embeddings | +| General usages in POST | `post_api` | *Huggingface* and *ModelScope* Inference API, and other customized post API | Each API has its specific configuration requirements. For example, to configure an OpenAI API, you would need to fill out the following fields in the model config in a dict, a yaml file or a json file: ```python model_config = { - "type": "openai", # Choose from "openai", "openai_dall_e", or "openai_embedding" - "name": "{your_config_name}", # A unique identifier for your config - "model_name": "{model_name}", # The model identifier used in the OpenAI API, such as "gpt-3.5-turbo", "gpt-4", or "text-embedding-ada-002" + "model_id": "{your_model_id}", # A unique identifier for the generated model wrapper + "model_type": "openai", # Choose from "openai", "openai_dall_e", or "openai_embedding" + "model": "{model_name}", # The model identifier used in the OpenAI API, such as "gpt-3.5-turbo", "gpt-4", or "text-embedding-ada-002" "api_key": "xxx", # Your OpenAI API key. If unset, the environment variable OPENAI_API_KEY is used. "organization": "xxx", # Your OpenAI organization ID. If unset, the environment variable OPENAI_ORGANIZATION is used. } @@ -52,7 +52,7 @@ from agentscope.agents import DialogAgent, UserAgent agentscope.init(model_configs="./openai_model_configs.json") # Create a dialog agent and a user agent -dialogAgent = DialogAgent(name="assistant", model="gpt-4") +dialogAgent = DialogAgent(name="assistant", model_id="gpt-4") userAgent = UserAgent() ``` diff --git a/docs/sphinx_doc/source/tutorial/104-usecase.md b/docs/sphinx_doc/source/tutorial/104-usecase.md index f1502cfcf..a93d50f34 100644 --- a/docs/sphinx_doc/source/tutorial/104-usecase.md +++ b/docs/sphinx_doc/source/tutorial/104-usecase.md @@ -35,11 +35,12 @@ As we discussed in the last tutorial, you need to prepare your model configurati ```json [ { - "type": "openai", - "name": "gpt-4", - "parameters": { - "api_key": "xxx", - "organization_id": "xxx", + "model_type": "openai", + "model_id": "gpt-4", + "model": "gpt-4", + "api_key": "xxx", + "organization": "xxx", + "generate_args": { "temperature": 0.0 } }, @@ -75,7 +76,7 @@ AgentScope provides several out-of-the-box Agents implements and organizes them "args": { "name": "Player1", "sys_prompt": "Act as a player in a werewolf game. You are Player1 and\nthere are totally 6 players, named Player1, Player2, Player3, Player4, Player5 and Player6.\n\nPLAYER ROLES:\nIn werewolf game, players are divided into two werewolves, two villagers, one seer, and one witch. Note only werewolves know who are their teammates.\nWerewolves: They know their teammates' identities and attempt to eliminate a villager each night while trying to remain undetected.\nVillagers: They do not know who the werewolves are and must work together during the day to deduce who the werewolves might be and vote to eliminate them.\nSeer: A villager with the ability to learn the true identity of one player each night. This role is crucial for the villagers to gain information.\nWitch: A character who has a one-time ability to save a player from being eliminated at night (sometimes this is a potion of life) and a one-time ability to eliminate a player at night (a potion of death).\n\nGAME RULE:\nThe game consists of two phases: night phase and day phase. The two phases are repeated until werewolf or villager wins the game.\n1. Night Phase: During the night, the werewolves discuss and vote for a player to eliminate. Special roles also perform their actions at this time (e.g., the Seer chooses a player to learn their role, the witch chooses a decide if save the player).\n2. Day Phase: During the day, all surviving players discuss who they suspect might be a werewolf. No one reveals their role unless it serves a strategic purpose. After the discussion, a vote is taken, and the player with the most votes is \"lynched\" or eliminated from the game.\n\nVICTORY CONDITION:\nFor werewolves, they win the game if the number of werewolves is equal to or greater than the number of remaining villagers.\nFor villagers, they win if they identify and eliminate all of the werewolves in the group.\n\nCONSTRAINTS:\n1. Your response should be in the first person.\n2. This is a conversational game. You should respond only based on the conversation history and your strategy.\n\nYou are playing werewolf in this game.\n", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } } diff --git a/docs/sphinx_doc/source/tutorial/201-agent.md b/docs/sphinx_doc/source/tutorial/201-agent.md index b7e648eba..a1dffc5a8 100644 --- a/docs/sphinx_doc/source/tutorial/201-agent.md +++ b/docs/sphinx_doc/source/tutorial/201-agent.md @@ -32,13 +32,13 @@ class AgentBase(Operator): name: str, config: Optional[dict] = None, sys_prompt: Optional[str] = None, - model: Optional[Union[Callable[..., Any], str]] = None, + model_id: str = None, use_memory: bool = True, memory_config: Optional[dict] = None, ) -> None: # ... [code omitted for brevity] - def observe(self, x: Union[dict, Sequence[dict]]) -> None: + def observe(self, x: Union[dict, Sequence[dict]]) -> None: # An optional method for updating the agent's internal state based on # messages it has observed. This method can be used to enrich the # agent's understanding and memory without producing an immediate @@ -109,7 +109,7 @@ from agentscope.agents import DialogAgent # Configuration for the DialogAgent dialog_agent_config = { "name": "ServiceBot", - "model": "gpt-3.5", # Specify the model used for dialogue generation + "model_id": "gpt-3.5", # Specify the model used for dialogue generation "sys_prompt": "Act as AI assistant to interact with the others. Try to " "reponse on one line.\n", # Custom prompt for the agent # Other configurations specific to the DialogAgent diff --git a/docs/sphinx_doc/source/tutorial/203-model.md b/docs/sphinx_doc/source/tutorial/203-model.md index 92c8fde7c..8c4859cef 100644 --- a/docs/sphinx_doc/source/tutorial/203-model.md +++ b/docs/sphinx_doc/source/tutorial/203-model.md @@ -15,23 +15,25 @@ where the model configs could be a list of dict: ```json [ { - "type": "openai", - "name": "gpt-4", - "parameters": { - "api_key": "xxx", - "organization_id": "xxx", + "model_id": "gpt-4-temperature-0.0", + "model_type": "openai", + "model": "gpt-4", + "api_key": "xxx", + "organization": "xxx", + "generate_args": { "temperature": 0.0 } }, { - "type": "openai_dall_e", - "name": "dall-e-3", - "parameters": { - "api_key": "xxx", - "organization_id": "xxx", + "model_id": "dall-e-3-size-1024x1024", + "model_type": "openai_dall_e", + "model": "dall-e-3", + "api_key": "xxx", + "organization_id": "xxx", + "generate_args": { "size": "1024x1024" } - } + }, // Additional models can be configured here ] ``` @@ -86,8 +88,8 @@ In AgentScope, you can load the model with the following model configs: `./flask ```json { - "type": "post_api", - "name": "flask_llama2-7b-chat", + "model_type": "post_api", + "model_id": "flask_llama2-7b-chat", "api_url": "http://127.0.0.1:8000/llm/", "json_args": { "max_length": 4096, @@ -127,8 +129,8 @@ In AgentScope, you can load the model with the following model configs: `flask_m ```json { - "type": "post_api", - "name": "flask_llama2-7b-ms", + "model_type": "post_api", + "model_id": "flask_llama2-7b-ms", "api_url": "http://127.0.0.1:8000/llm/", "json_args": { "max_length": 4096, @@ -169,8 +171,8 @@ Now you can load the model in AgentScope by the following model config: `fastcha ```json { - "type": "openai", - "name": "meta-llama/Llama-2-7b-chat-hf", + "model_id": "meta-llama/Llama-2-7b-chat-hf", + "model_type": "openai", "api_key": "EMPTY", "client_args": { "base_url": "http://127.0.0.1:8000/v1/" @@ -209,8 +211,8 @@ Now you can load the model in AgentScope by the following model config: `vllm_sc ```json { - "type": "openai", - "name": "meta-llama/Llama-2-7b-chat-hf", + "model_id": "meta-llama/Llama-2-7b-chat-hf", + "model_type": "openai", "api_key": "EMPTY", "client_args": { "base_url": "http://127.0.0.1:8000/v1/" @@ -228,8 +230,8 @@ Taking `gpt2` in HuggingFace inference API as an example, you can use the follow ```json { - "type": "post_api", - "name": 'gpt2', + "model_id": "gpt2", + "model_type": "post_api", "headers": { "Authorization": "Bearer {YOUR_API_TOKEN}" } @@ -248,7 +250,7 @@ model = AutoModelForCausalLM.from_pretrained(MODEL_NAME) tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME) model.eval() # Do remember to re-implement the `reply` method to tokenize *message*! -agent = YourAgent(name='agent', model=model, tokenizer=tokenizer) +agent = YourAgent(name='agent', model_id=model_id, tokenizer=tokenizer) ``` [[Return to the top]](#using-different-model-sources-with-model-api) diff --git a/examples/conversation/conversation.py b/examples/conversation/conversation.py index f97439cb5..3ed2aa73c 100644 --- a/examples/conversation/conversation.py +++ b/examples/conversation/conversation.py @@ -8,8 +8,9 @@ agentscope.init( model_configs=[ { - "type": "openai", - "name": "gpt-3.5-turbo", + "model_type": "openai", + "model_id": "gpt-3.5-turbo", + "model": "gpt-3.5-turbo", "api_key": "xxx", # Load from env if not provided "organization": "xxx", # Load from env if not provided "generate_args": { @@ -17,8 +18,8 @@ }, }, { - "type": "post_api", - "name": "my_post_api", + "model_type": "post_api", + "model_id": "my_post_api", "api_url": "https://xxx", "headers": {}, }, @@ -29,7 +30,7 @@ dialog_agent = DialogAgent( name="Assistant", sys_prompt="You're a helpful assistant.", - model="gpt-3.5-turbo", # replace by your model config name + model_id="gpt-3.5-turbo", # replace by your model config name ) user_agent = UserAgent() diff --git a/examples/distributed/configs/debate_agent_configs.json b/examples/distributed/configs/debate_agent_configs.json index dec5af779..1d9b65bde 100644 --- a/examples/distributed/configs/debate_agent_configs.json +++ b/examples/distributed/configs/debate_agent_configs.json @@ -4,7 +4,7 @@ "args": { "name": "Pro", "sys_prompt": "Assume the role of a debater who is arguing in favor of the proposition that AGI (Artificial General Intelligence) can be achieved using the GPT model framework. Construct a coherent and persuasive argument, including scientific, technological, and theoretical evidence, to support the statement that GPT models are a viable path to AGI. Highlight the advancements in language understanding, adaptability, and scalability of GPT models as key factors in progressing towards AGI.", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } }, @@ -13,7 +13,7 @@ "args": { "name": "Con", "sys_prompt": "Assume the role of a debater who is arguing against the proposition that AGI can be achieved using the GPT model framework. Construct a coherent and persuasive argument, including scientific, technological, and theoretical evidence, to support the statement that GPT models, while impressive, are insufficient for reaching AGI. Discuss the limitations of GPT models such as lack of understanding, consciousness, ethical reasoning, and general problem-solving abilities that are essential for true AGI.", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } }, @@ -22,7 +22,7 @@ "args": { "name": "Judge", "sys_prompt": "Assume the role of an impartial judge in a debate where the affirmative side argues that AGI can be achieved using the GPT model framework, and the negative side contests this. Listen to both sides' arguments and provide an analytical judgment on which side presented a more compelling and reasonable case. Consider the strength of the evidence, the persuasiveness of the reasoning, and the overall coherence of the arguments presented by each side.", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } } diff --git a/examples/distributed/configs/model_configs.json b/examples/distributed/configs/model_configs.json index 20bfc1501..de1fb602d 100644 --- a/examples/distributed/configs/model_configs.json +++ b/examples/distributed/configs/model_configs.json @@ -1,20 +1,22 @@ [ { - "type": "openai", - "name": "gpt-3.5-turbo", - "parameters": { - "api_key": "xxx", - "organization_id": "xxx", + "model_id": "gpt-3.5-turbo", + "model_type": "openai", + "model": "gpt-3.5-turbo", + "api_key": "xxx", + "organization": "xxx", + "generate_args": { "temperature": 0.0 } }, { - "type": "openai", - "name": "gpt-4", - "parameters": { - "api_key": "xxx", - "organization_id": "xxx", - "temperature": 0.0 + "model_id": "gpt-4", + "model_type": "openai", + "model": "gpt-4", + "api_key": "xxx", + "organization": "xxx", + "generate_args": { + "temperature": 0.5 } } ] \ No newline at end of file diff --git a/examples/distributed/distributed_dialog.py b/examples/distributed/distributed_dialog.py index 0a6f30e51..b2cad8e84 100644 --- a/examples/distributed/distributed_dialog.py +++ b/examples/distributed/distributed_dialog.py @@ -41,7 +41,7 @@ def setup_assistant_server(assistant_host: str, assistant_port: int) -> None: agent_kwargs={ "name": "Assitant", "sys_prompt": "You are a helpful assistant.", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": True, }, host=assistant_host, @@ -59,7 +59,7 @@ def run_main_process(assistant_host: str, assistant_port: int) -> None: assistant_agent = DialogAgent( name="Assistant", sys_prompt="You are a helpful assistant.", - model="gpt-3.5-turbo", + model_id="gpt-3.5-turbo", use_memory=True, ).to_dist( host=assistant_host, diff --git a/examples/werewolf/README.md b/examples/werewolf/README.md index 90e275329..7166bbb0f 100644 --- a/examples/werewolf/README.md +++ b/examples/werewolf/README.md @@ -41,7 +41,7 @@ is as follows "args": { "name": "Player1", "sys_prompt": "Act as a player in a werewolf game. You are Player1 and\nthere are totally 6 players, named Player1, Player2, Player3, Player4, Player5 and Player6.\n\nPLAYER ROLES:\nIn werewolf game, players are divided into two werewolves, two villagers, one seer and one witch. Note only werewolves know who are their teammates.\nWerewolves: They know their teammates' identities and attempt to eliminate a villager each night while trying to remain undetected.\nVillagers: They do not know who the werewolves are and must work together during the day to deduce who the werewolves might be and vote to eliminate them.\nSeer: A villager with the ability to learn the true identity of one player each night. This role is crucial for the villagers to gain information.\nWitch: A character who has a one-time ability to save a player from being eliminated at night (sometimes this is a potion of life) and a one-time ability to eliminate a player at night (a potion of death).\n\nGAME RULE:\nThe game is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win the game.\n1. Night Phase: During the night, the werewolves discuss and vote for a player to eliminate. Special roles also perform their actions at this time (e.g., the Seer chooses a player to learn their role, the witch chooses a decide if save the player).\n2. Day Phase: During the day, all surviving players discuss who they suspect might be a werewolf. No one reveals their role unless it serves a strategic purpose. After the discussion, a vote is taken, and the player with the most votes is \"lynched\" or eliminated from the game.\n\nVICTORY CONDITION:\nFor werewolves, they win the game if the number of werewolves is equal to or greater than the number of remaining villagers.\nFor villagers, they win if they identify and eliminate all of the werewolves in the group.\n\nCONSTRAINTS:\n1. Your response should be in the first person.\n2. This is a conversational game. You should response only based on the conversation history and your strategy.\n\nYou are playing werewolf in this game.\n", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } } @@ -95,4 +95,4 @@ More details please refer to the code of [`DictDialogAgent`](../.. if x.get("agreement", False): break # ... -``` \ No newline at end of file +``` diff --git a/examples/werewolf/configs/agent_configs.json b/examples/werewolf/configs/agent_configs.json index 9c873458d..599c1fe6b 100644 --- a/examples/werewolf/configs/agent_configs.json +++ b/examples/werewolf/configs/agent_configs.json @@ -4,7 +4,7 @@ "args": { "name": "Player1", "sys_prompt": "Act as a player in a werewolf game. You are Player1 and\nthere are totally 6 players, named Player1, Player2, Player3, Player4, Player5 and Player6.\n\nPLAYER ROLES:\nIn werewolf game, players are divided into two werewolves, two villagers, one seer and one witch. Note only werewolves know who are their teammates.\nWerewolves: They know their teammates' identities and attempt to eliminate a villager each night while trying to remain undetected.\nVillagers: They do not know who the werewolves are and must work together during the day to deduce who the werewolves might be and vote to eliminate them.\nSeer: A villager with the ability to learn the true identity of one player each night. This role is crucial for the villagers to gain information.\nWitch: A character who has a one-time ability to save a player from being eliminated at night (sometimes this is a potion of life) and a one-time ability to eliminate a player at night (a potion of death).\n\nGAME RULE:\nThe game is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win the game.\n1. Night Phase: During the night, the werewolves discuss and vote for a player to eliminate. Special roles also perform their actions at this time (e.g., the Seer chooses a player to learn their role, the witch chooses a decide if save the player).\n2. Day Phase: During the day, all surviving players discuss who they suspect might be a werewolf. No one reveals their role unless it serves a strategic purpose. After the discussion, a vote is taken, and the player with the most votes is \"lynched\" or eliminated from the game.\n\nVICTORY CONDITION:\nFor werewolves, they win the game if the number of werewolves is equal to or greater than the number of remaining villagers.\nFor villagers, they win if they identify and eliminate all of the werewolves in the group.\n\nCONSTRAINTS:\n1. Your response should be in the first person.\n2. This is a conversational game. You should response only based on the conversation history and your strategy.\n\nYou are playing werewolf in this game.\n", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } }, @@ -13,7 +13,7 @@ "args": { "name": "Player2", "sys_prompt": "Act as a player in a werewolf game. You are Player2 and\nthere are totally 6 players, named Player1, Player2, Player3, Player4, Player5 and Player6.\n\nPLAYER ROLES:\nIn werewolf game, players are divided into two werewolves, two villagers, one seer and one witch. Note only werewolves know who are their teammates.\nWerewolves: They know their teammates' identities and attempt to eliminate a villager each night while trying to remain undetected.\nVillagers: They do not know who the werewolves are and must work together during the day to deduce who the werewolves might be and vote to eliminate them.\nSeer: A villager with the ability to learn the true identity of one player each night. This role is crucial for the villagers to gain information.\nWitch: A character who has a one-time ability to save a player from being eliminated at night (sometimes this is a potion of life) and a one-time ability to eliminate a player at night (a potion of death).\n\nGAME RULE:\nThe game is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win the game.\n1. Night Phase: During the night, the werewolves discuss and vote for a player to eliminate. Special roles also perform their actions at this time (e.g., the Seer chooses a player to learn their role, the witch chooses a decide if save the player).\n2. Day Phase: During the day, all surviving players discuss who they suspect might be a werewolf. No one reveals their role unless it serves a strategic purpose. After the discussion, a vote is taken, and the player with the most votes is \"lynched\" or eliminated from the game.\n\nVICTORY CONDITION:\nFor werewolves, they win the game if the number of werewolves is equal to or greater than the number of remaining villagers.\nFor villagers, they win if they identify and eliminate all of the werewolves in the group.\n\nCONSTRAINTS:\n1. Your response should be in the first person.\n2. This is a conversational game. You should response only based on the conversation history and your strategy.\n\nYou are playing werewolf in this game.\n", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } }, @@ -22,7 +22,7 @@ "args": { "name": "Player3", "sys_prompt": "Act as a player in a werewolf game. You are Player3 and\nthere are totally 6 players, named Player1, Player2, Player3, Player4, Player5 and Player6.\n\nPLAYER ROLES:\nIn werewolf game, players are divided into two werewolves, two villagers, one seer and one witch. Note only werewolves know who are their teammates.\nWerewolves: They know their teammates' identities and attempt to eliminate a villager each night while trying to remain undetected.\nVillagers: They do not know who the werewolves are and must work together during the day to deduce who the werewolves might be and vote to eliminate them.\nSeer: A villager with the ability to learn the true identity of one player each night. This role is crucial for the villagers to gain information.\nWitch: A character who has a one-time ability to save a player from being eliminated at night (sometimes this is a potion of life) and a one-time ability to eliminate a player at night (a potion of death).\n\nGAME RULE:\nThe game is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win the game.\n1. Night Phase: During the night, the werewolves discuss and vote for a player to eliminate. Special roles also perform their actions at this time (e.g., the Seer chooses a player to learn their role, the witch chooses a decide if save the player).\n2. Day Phase: During the day, all surviving players discuss who they suspect might be a werewolf. No one reveals their role unless it serves a strategic purpose. After the discussion, a vote is taken, and the player with the most votes is \"lynched\" or eliminated from the game.\n\nVICTORY CONDITION:\nFor werewolves, they win the game if the number of werewolves is equal to or greater than the number of remaining villagers.\nFor villagers, they win if they identify and eliminate all of the werewolves in the group.\n\nCONSTRAINTS:\n1. Your response should be in the first person.\n2. This is a conversational game. You should response only based on the conversation history and your strategy.\n\nYou are playing villager in this game.\n", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } }, @@ -31,7 +31,7 @@ "args": { "name": "Player4", "sys_prompt": "Act as a player in a werewolf game. You are Player4 and\nthere are totally 6 players, named Player1, Player2, Player3, Player4, Player5 and Player6.\n\nPLAYER ROLES:\nIn werewolf game, players are divided into two werewolves, two villagers, one seer and one witch. Note only werewolves know who are their teammates.\nWerewolves: They know their teammates' identities and attempt to eliminate a villager each night while trying to remain undetected.\nVillagers: They do not know who the werewolves are and must work together during the day to deduce who the werewolves might be and vote to eliminate them.\nSeer: A villager with the ability to learn the true identity of one player each night. This role is crucial for the villagers to gain information.\nWitch: A character who has a one-time ability to save a player from being eliminated at night (sometimes this is a potion of life) and a one-time ability to eliminate a player at night (a potion of death).\n\nGAME RULE:\nThe game is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win the game.\n1. Night Phase: During the night, the werewolves discuss and vote for a player to eliminate. Special roles also perform their actions at this time (e.g., the Seer chooses a player to learn their role, the witch chooses a decide if save the player).\n2. Day Phase: During the day, all surviving players discuss who they suspect might be a werewolf. No one reveals their role unless it serves a strategic purpose. After the discussion, a vote is taken, and the player with the most votes is \"lynched\" or eliminated from the game.\n\nVICTORY CONDITION:\nFor werewolves, they win the game if the number of werewolves is equal to or greater than the number of remaining villagers.\nFor villagers, they win if they identify and eliminate all of the werewolves in the group.\n\nCONSTRAINTS:\n1. Your response should be in the first person.\n2. This is a conversational game. You should response only based on the conversation history and your strategy.\n\nYou are playing villager in this game.\n", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } }, @@ -40,7 +40,7 @@ "args": { "name": "Player5", "sys_prompt": "Act as a player in a werewolf game. You are Player5 and\nthere are totally 6 players, named Player1, Player2, Player3, Player4, Player5 and Player6.\n\nPLAYER ROLES:\nIn werewolf game, players are divided into two werewolves, two villagers, one seer and one witch. Note only werewolves know who are their teammates.\nWerewolves: They know their teammates' identities and attempt to eliminate a villager each night while trying to remain undetected.\nVillagers: They do not know who the werewolves are and must work together during the day to deduce who the werewolves might be and vote to eliminate them.\nSeer: A villager with the ability to learn the true identity of one player each night. This role is crucial for the villagers to gain information.\nWitch: A character who has a one-time ability to save a player from being eliminated at night (sometimes this is a potion of life) and a one-time ability to eliminate a player at night (a potion of death).\n\nGAME RULE:\nThe game is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win the game.\n1. Night Phase: During the night, the werewolves discuss and vote for a player to eliminate. Special roles also perform their actions at this time (e.g., the Seer chooses a player to learn their role, the witch chooses a decide if save the player).\n2. Day Phase: During the day, all surviving players discuss who they suspect might be a werewolf. No one reveals their role unless it serves a strategic purpose. After the discussion, a vote is taken, and the player with the most votes is \"lynched\" or eliminated from the game.\n\nVICTORY CONDITION:\nFor werewolves, they win the game if the number of werewolves is equal to or greater than the number of remaining villagers.\nFor villagers, they win if they identify and eliminate all of the werewolves in the group.\n\nCONSTRAINTS:\n1. Your response should be in the first person.\n2. This is a conversational game. You should response only based on the conversation history and your strategy.\n\nYou are playing seer in this game.\n", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } }, @@ -49,7 +49,7 @@ "args": { "name": "Player6", "sys_prompt": "Act as a player in a werewolf game. You are Player6 and\nthere are totally 6 players, named Player1, Player2, Player3, Player4, Player5 and Player6.\n\nPLAYER ROLES:\nIn werewolf game, players are divided into two werewolves, two villagers, one seer and one witch. Note only werewolves know who are their teammates.\nWerewolves: They know their teammates' identities and attempt to eliminate a villager each night while trying to remain undetected.\nVillagers: They do not know who the werewolves are and must work together during the day to deduce who the werewolves might be and vote to eliminate them.\nSeer: A villager with the ability to learn the true identity of one player each night. This role is crucial for the villagers to gain information.\nWitch: A character who has a one-time ability to save a player from being eliminated at night (sometimes this is a potion of life) and a one-time ability to eliminate a player at night (a potion of death).\n\nGAME RULE:\nThe game is consisted of two phases: night phase and day phase. The two phases are repeated until werewolf or villager win the game.\n1. Night Phase: During the night, the werewolves discuss and vote for a player to eliminate. Special roles also perform their actions at this time (e.g., the Seer chooses a player to learn their role, the witch chooses a decide if save the player).\n2. Day Phase: During the day, all surviving players discuss who they suspect might be a werewolf. No one reveals their role unless it serves a strategic purpose. After the discussion, a vote is taken, and the player with the most votes is \"lynched\" or eliminated from the game.\n\nVICTORY CONDITION:\nFor werewolves, they win the game if the number of werewolves is equal to or greater than the number of remaining villagers.\nFor villagers, they win if they identify and eliminate all of the werewolves in the group.\n\nCONSTRAINTS:\n1. Your response should be in the first person.\n2. This is a conversational game. You should response only based on the conversation history and your strategy.\n\nYou are playing witch in this game.\n", - "model": "gpt-3.5-turbo", + "model_id": "gpt-3.5-turbo", "use_memory": true } } diff --git a/examples/werewolf/configs/model_configs.json b/examples/werewolf/configs/model_configs.json index 3f7dbcbc2..1d1720d57 100644 --- a/examples/werewolf/configs/model_configs.json +++ b/examples/werewolf/configs/model_configs.json @@ -1,7 +1,8 @@ [ { - "type": "openai", - "name": "gpt-4", + "model_type": "openai", + "model_id": "gpt-4", + "model": "gpt-4", "api_key": "xxx", "organization": "xxx", "generate_args": { @@ -9,12 +10,10 @@ } }, { - "type": "post_api", - "name": "my_post_api", + "model_type": "post_api", + "model_id": "my_post_api", "api_url": "https://xxx", - "headers": { - }, - "json_args": { - } + "headers": {}, + "json_args": {} } -] +] \ No newline at end of file diff --git a/scripts/README.md b/scripts/README.md index cc16ea93f..afd37e43b 100644 --- a/scripts/README.md +++ b/scripts/README.md @@ -6,28 +6,28 @@ fast set up local model API serving with different inference engines. Table of Contents ================= -* [Local Model API Serving](#local-model-api-serving) - * [Flask-based Model API Serving](#flask-based-model-api-serving) - * [With Transformers Library](#with-transformers-library) - * [Install Libraries and Set up Serving](#install-libraries-and-set-up-serving) - * [How to use in AgentScope](#how-to-use-in-agentscope) - * [Note](#note) - * [With ModelScope Library](#with-modelscope-library) - * [Install Libraries and Set up Serving](#install-libraries-and-set-up-serving-1) - * [How to use in AgentScope](#how-to-use-in-agentscope-1) - * [Note](#note-1) - * [FastChat](#fastchat) - * [Install Libraries and Set up Serving](#install-libraries-and-set-up-serving-2) - * [Supported Models](#supported-models) - * [How to use in AgentScope](#how-to-use-in-agentscope-2) - * [vllm](#vllm) - * [Install Libraries and Set up Serving](#install-libraries-and-set-up-serving-3) - * [Supported Models](#supported-models-1) - * [How to use in AgentScope](#how-to-use-in-agentscope-3) -* [Model Inference API](#model-inference-api) - - +- [Set up Model API Serving](#set-up-model-api-serving) +- [Table of Contents](#table-of-contents) + - [Local Model API Serving](#local-model-api-serving) + - [Flask-based Model API Serving](#flask-based-model-api-serving) + - [With Transformers Library](#with-transformers-library) + - [Install Libraries and Set up Serving](#install-libraries-and-set-up-serving) + - [How to use in AgentScope](#how-to-use-in-agentscope) + - [Note](#note) + - [With ModelScope Library](#with-modelscope-library) + - [Install Libraries and Set up Serving](#install-libraries-and-set-up-serving-1) + - [How to use in AgentScope](#how-to-use-in-agentscope-1) + - [Note](#note-1) + - [FastChat](#fastchat) + - [Install Libraries and Set up Serving](#install-libraries-and-set-up-serving-2) + - [Supported Models](#supported-models) + - [How to use in AgentScope](#how-to-use-in-agentscope-2) + - [vllm](#vllm) + - [Install Libraries and Set up Serving](#install-libraries-and-set-up-serving-3) + - [Supported models](#supported-models-1) + - [How to use in AgentScope](#how-to-use-in-agentscope-3) + - [Model Inference API](#model-inference-api) ## Local Model API Serving @@ -51,6 +51,7 @@ pip install Flask, transformers Taking model `meta-llama/Llama-2-7b-chat-hf` and port `8000` as an example, set up the model API serving by running the following command. + ```bash python flask_transformers/setup_hf_service.py --model_name_or_path meta-llama/Llama-2-7b-chat-hf @@ -67,8 +68,8 @@ In AgentScope, you can load the model with the following model configs: `./flask ```json { - "type": "post_api", - "name": "flask_llama2-7b-chat", + "model_type": "post_api", + "model_id": "flask_llama2-7b-chat", "api_url": "http://127.0.0.1:8000/llm/", "json_args": { "max_length": 4096, @@ -83,7 +84,6 @@ In this model serving, the messages from post requests should be in **STRING format**. You can use [templates for chat model](https://huggingface.co/docs/transformers/main/chat_templating) in transformers with a little modification in `./flask_transformers/setup_hf_service.py`. - #### With ModelScope Library ##### Install Libraries and Set up Serving @@ -107,7 +107,6 @@ python flask_modelscope/setup_ms_service.py You can replace `modelscope/Llama-2-7b-ms` with any model card in modelscope model hub. - ##### How to use in AgentScope In AgentScope, you can load the model with the following model configs: @@ -115,8 +114,8 @@ In AgentScope, you can load the model with the following model configs: ```json { - "type": "post_api", - "name": "flask_llama2-7b-ms", + "model_type": "post_api", + "model_id": "flask_llama2-7b-ms", "api_url": "http://127.0.0.1:8000/llm/", "json_args": { "max_length": 4096, @@ -130,7 +129,6 @@ In AgentScope, you can load the model with the following model configs: Similar with the example of transformers, the messages from post requests should be in **STRING format**. - ### FastChat [FastChat](https://github.com/lm-sys/FastChat) is an open platform that @@ -152,16 +150,19 @@ bash fastchat_script/fastchat_setup.sh -m meta-llama/Llama-2-7b-chat-hf -p 8000 ``` #### Supported Models + Refer to [supported model list](https://github.com/lm-sys/FastChat/blob/main/docs/model_support.md#supported-models) of FastChat. #### How to use in AgentScope + Now you can load the model in AgentScope by the following model config: `fastchat_script/model_config.json`. + ```json { - "type": "openai", - "name": "meta-llama/Llama-2-7b-chat-hf", + "model_type": "openai", + "model_id": "meta-llama/Llama-2-7b-chat-hf", "api_key": "EMPTY", "client_args": { "base_url": "http://127.0.0.1:8000/v1/" @@ -178,6 +179,7 @@ Now you can load the model in AgentScope by the following model config: `fastcha and serving engine for LLMs. #### Install Libraries and Set up Serving + To install vllm, run ```bash @@ -198,12 +200,13 @@ Please refer to the of vllm. #### How to use in AgentScope + Now you can load the model in AgentScope by the following model config: `vllm_script/model_config.json`. ```json { - "type": "openai", - "name": "meta-llama/Llama-2-7b-chat-hf", + "model_type": "openai", + "model_id": "meta-llama/Llama-2-7b-chat-hf", "api_key": "EMPTY", "client_args": { "base_url": "http://127.0.0.1:8000/v1/" @@ -214,7 +217,6 @@ Now you can load the model in AgentScope by the following model config: `vllm_sc } ``` - ## Model Inference API Both [Huggingface](https://huggingface.co/docs/api-inference/index) and @@ -223,13 +225,13 @@ which can be used with AgentScope post api model wrapper. Taking `gpt2` in HuggingFace inference API as an example, you can use the following model config in AgentScope. -```bash +```json { - "type": "post_api", - "name": 'gpt2', + "model_type": "post_api", + "model_id": "gpt2", "headers": { "Authorization": "Bearer {YOUR_API_TOKEN}" - } + }, "api_url": "https://api-inference.huggingface.co/models/gpt2" } -``` \ No newline at end of file +``` diff --git a/scripts/faschat/model_config.json b/scripts/faschat/model_config.json index 9e812662c..0dd81ad61 100644 --- a/scripts/faschat/model_config.json +++ b/scripts/faschat/model_config.json @@ -1,7 +1,7 @@ { - "type": "openai", - "name": "fs-llama-2", - "model_name": "llama-2", + "model_type": "openai", + "model_id": "fs-llama-2", + "model": "llama-2", "api_key": "EMPTY", "client_args": { "base_url": "http:localhost:8000/v1/" diff --git a/scripts/flask_modelscope/model_config.json b/scripts/flask_modelscope/model_config.json index 10258bfac..89db934a5 100644 --- a/scripts/flask_modelscope/model_config.json +++ b/scripts/flask_modelscope/model_config.json @@ -1,6 +1,6 @@ { - "type": "post_api", - "name": "post_llama-2-chat-7b-ms", + "model_type": "post_api", + "model_id": "post_llama-2-chat-7b-ms", "api_url": "http://127.0.0.1:8000/llm/", "json_args": { "max_length": 4096, diff --git a/scripts/flask_transformers/model_config.json b/scripts/flask_transformers/model_config.json index bb81d1302..00d443c5b 100644 --- a/scripts/flask_transformers/model_config.json +++ b/scripts/flask_transformers/model_config.json @@ -1,6 +1,6 @@ { - "type": "post_api", - "name": "post_llama-2-chat-7b-hf", + "model_type": "post_api", + "model_id": "post_llama-2-chat-7b-hf", "api_url": "http://127.0.0.1:8000/llm/", "json_args": { "max_length": 4096, diff --git a/scripts/vllm_script/model_config.json b/scripts/vllm_script/model_config.json index 4d5895fb8..c6390a280 100644 --- a/scripts/vllm_script/model_config.json +++ b/scripts/vllm_script/model_config.json @@ -1,7 +1,7 @@ { - "type": "openai", - "name": "vllm-llama-2", - "model_name": "llama-2", + "model_type": "openai", + "model_id": "vllm-llama-2", + "model": "llama-2", "api_key": "EMPTY", "client_args": { "base_url": "http:localhost:8000/v1/" @@ -9,4 +9,4 @@ "generate_args": { "temperature": 0.5 } -} +} \ No newline at end of file diff --git a/src/agentscope/_init.py b/src/agentscope/_init.py index 85fc0e625..4ee340d1f 100644 --- a/src/agentscope/_init.py +++ b/src/agentscope/_init.py @@ -142,10 +142,6 @@ def init_process( logger_level (`LOG_LEVEL`, defaults to `"INFO"`): The logging level of logger. """ - # Load model configs if needed - if model_configs is not None: - read_model_configs(model_configs) - # Init the runtime _runtime.project = project _runtime.name = name @@ -161,3 +157,7 @@ def init_process( # Init logger dir_log = str(file_manager.dir_log) if save_log else None setup_logger(dir_log, logger_level) + + # Load model configs if needed + if model_configs is not None: + read_model_configs(model_configs) diff --git a/src/agentscope/agents/agent.py b/src/agentscope/agents/agent.py index 18cca85b0..aeb0ea73c 100644 --- a/src/agentscope/agents/agent.py +++ b/src/agentscope/agents/agent.py @@ -7,11 +7,10 @@ from typing import Sequence from typing import Union from typing import Any -from typing import Callable from loguru import logger from agentscope.agents.operator import Operator -from agentscope.models import load_model_by_name +from agentscope.models import load_model_by_id from agentscope.memory import TemporaryMemory @@ -38,7 +37,7 @@ def __init__( name: str, config: Optional[dict] = None, sys_prompt: Optional[str] = None, - model: Optional[Union[Callable[..., Any], str]] = None, + model_id: str = None, use_memory: bool = True, memory_config: Optional[dict] = None, ) -> None: @@ -54,10 +53,8 @@ def __init__( sys_prompt (`Optional[str]`): The system prompt of the agent, which can be passed by args or hard-coded in the agent. - model (`Optional[Union[Callable[..., Any], str]]`, defaults to - None): - The callable model object or the model name, which is used to - load model from configuration. + model_id (`str`, defaults to None): + The model id, which is used to load model from configuration. use_memory (`bool`, defaults to `True`): Whether the agent has memory. memory_config (`Optional[dict]`): @@ -71,11 +68,9 @@ def __init__( if sys_prompt is not None: self.sys_prompt = sys_prompt - if model is not None: - if isinstance(model, str): - self.model = load_model_by_name(model) - else: - self.model = model + # TODO: support to receive a ModelWrapper instance + if model_id is not None: + self.model = load_model_by_id(model_id) if use_memory: self.memory = TemporaryMemory(memory_config) diff --git a/src/agentscope/agents/dialog_agent.py b/src/agentscope/agents/dialog_agent.py index 1f749c289..5fe88ecf6 100644 --- a/src/agentscope/agents/dialog_agent.py +++ b/src/agentscope/agents/dialog_agent.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- """A general dialog agent.""" -from typing import Any, Optional, Union, Callable +from typing import Optional from loguru import logger from ..message import Msg @@ -18,7 +18,7 @@ def __init__( name: str, config: Optional[dict] = None, sys_prompt: Optional[str] = None, - model: Optional[Union[Callable[..., Any], str]] = None, + model_id: str = None, use_memory: bool = True, memory_config: Optional[dict] = None, prompt_type: Optional[PromptType] = PromptType.LIST, @@ -35,10 +35,8 @@ def __init__( sys_prompt (`Optional[str]`): The system prompt of the agent, which can be passed by args or hard-coded in the agent. - model (`Optional[Union[Callable[..., Any], str]]`, defaults to - None): - The callable model object or the model name, which is used to - load model from configuration. + model_id (`str`, defaults to None): + The model id, which is used to load model from configuration. use_memory (`bool`, defaults to `True`): Whether the agent has memory. memory_config (`Optional[dict]`): @@ -49,12 +47,12 @@ def __init__( `PromptType.LIST` or `PromptType.STRING`. """ super().__init__( - name, - config, - sys_prompt, - model, - use_memory, - memory_config, + name=name, + config=config, + sys_prompt=sys_prompt, + model_id=model_id, + use_memory=use_memory, + memory_config=memory_config, ) # init prompt engine diff --git a/src/agentscope/agents/dict_dialog_agent.py b/src/agentscope/agents/dict_dialog_agent.py index a6e66d9fe..ed150a99c 100644 --- a/src/agentscope/agents/dict_dialog_agent.py +++ b/src/agentscope/agents/dict_dialog_agent.py @@ -2,7 +2,7 @@ """A dict dialog agent that using `parse_func` and `fault_handler` to parse the model response.""" import json -from typing import Any, Optional, Union, Callable +from typing import Any, Optional, Callable from loguru import logger from ..message import Msg @@ -38,7 +38,7 @@ def __init__( name: str, config: Optional[dict] = None, sys_prompt: Optional[str] = None, - model: Optional[Union[Callable[..., Any], str]] = None, + model_id: str = None, use_memory: bool = True, memory_config: Optional[dict] = None, parse_func: Optional[Callable[..., Any]] = json.loads, @@ -58,10 +58,8 @@ def __init__( sys_prompt (`Optional[str]`, defaults to `None`): The system prompt of the agent, which can be passed by args or hard-coded in the agent. - model (`Optional[Union[Callable[..., Any], str]]`, defaults to - `None`): - The callable model object or the model name, which is used to - load model from configuration. + model_id (`str`, defaults to None): + The model id, which is used to load model from configuration. use_memory (`bool`, defaults to `True`): Whether the agent has memory. memory_config (`Optional[dict]`, defaults to `None`): @@ -82,12 +80,12 @@ def __init__( `PromptType.LIST` or `PromptType.STRING`. """ super().__init__( - name, - config, - sys_prompt, - model, - use_memory, - memory_config, + name=name, + config=config, + sys_prompt=sys_prompt, + model_id=model_id, + use_memory=use_memory, + memory_config=memory_config, ) # record the func and handler for parsing and handling faults diff --git a/src/agentscope/configs/__init__.py b/src/agentscope/configs/__init__.py deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/agentscope/configs/model_config.py b/src/agentscope/configs/model_config.py deleted file mode 100644 index 452a4d2c7..000000000 --- a/src/agentscope/configs/model_config.py +++ /dev/null @@ -1,122 +0,0 @@ -# -*- coding: utf-8 -*- -"""The model config.""" -from typing import Any -from ..constants import _DEFAULT_MAX_RETRIES -from ..constants import _DEFAULT_MESSAGES_KEY -from ..constants import _DEFAULT_API_BUDGET - - -class CfgBase(dict): - """Base class for model config.""" - - __getattr__ = dict.__getitem__ - __setattr__ = dict.__setitem__ - - def init(self, **kwargs: Any) -> None: - """Initialize the config with the given arguments, and checking the - type of the arguments.""" - cls = self.__class__ - for k, v in kwargs.items(): - if k in cls.__dict__["__annotations__"]: - required_type = cls.__dict__["__annotations__"][k] - if isinstance(v, required_type): - self[k] = v - else: - raise TypeError( - f"The argument {k} should be " - f"{required_type}, but got {type(v)} " - f"instead.", - ) - - -class OpenAICfg(CfgBase): - """The config for OpenAI models.""" - - type: str = "openai" - """The typing of this config.""" - - name: str - """The name of the model.""" - - model_name: str = None - """The name of the model, (e.g. gpt-4, gpt-3.5-turbo). Default to `name` if - not provided.""" - - api_key: str = None - """The api key used for OpenAI API. Will be read from env if not - provided.""" - - organization: str = None - """The organization used for OpenAI API. Will be read from env if not - provided.""" - - client_args: dict = None - """The arguments used to initialize openai client, e.g. `timeout`, - `max_retries`.""" - - generate_args: dict = None - """The arguments used in openai api generation, e.g. `temperature`, - `seed`.""" - - budget: float = _DEFAULT_API_BUDGET - """The total budget using this model. Set to `None` means no limit.""" - - -class PostApiCfg(CfgBase): - """The config for Post API. The final request post will be - - .. code-block:: python - - cfg = PostApiCfg(...) - request.post( - url=cfg.api_url, - headers=cfg.headers, - json={ - cfg.message_key: messages, - **cfg.json_args - }, - **cfg.post_args - ) - - """ - - type: str = "post_api" - """The typing of this config.""" - - name: str - """The name of the model.""" - - api_url: str - """The url of the model.""" - - headers: dict = {} - """The headers used for the request.""" - - max_length: int = 2048 - """The max length of the model, if not provided, defaults to 2048 in - model wrapper.""" - - # TODO: add support for timeout - timeout: int = 30 - """The timeout of the request.""" - - json_args: dict = None - """The additional arguments used within "json" keyword argument, - e.g. `request.post(json={..., **json_args})`, which depends on the - specific requirements of the model API.""" - - post_args: dict = None - """The keywords args used within `requests.post()`, e.g. `request.post(..., - **generate_args)`, which depends on the specific requirements of the - model API.""" - - max_retries: int = _DEFAULT_MAX_RETRIES - """The max retries of the request.""" - - messages_key: str = _DEFAULT_MESSAGES_KEY - """The key of the prompt messages in `requests.post()`, - e.g. `request.post(json={${messages_key}: messages, **json_args})`. For - huggingface and modelscope inference API, the key is `inputs`""" - - budget: float = _DEFAULT_API_BUDGET - """The total budget using this model. Set to `None` means no limit.""" diff --git a/src/agentscope/memory/temporary_memory.py b/src/agentscope/memory/temporary_memory.py index d95c8fad8..52f8c14f0 100644 --- a/src/agentscope/memory/temporary_memory.py +++ b/src/agentscope/memory/temporary_memory.py @@ -13,7 +13,7 @@ from loguru import logger from .memory import MemoryBase -from ..models import load_model_by_name +from ..models import load_model_by_id from ..service.retrieval.retrieval_from_list import retrieve_from_list from ..service.retrieval.similarity import Embedding @@ -34,7 +34,7 @@ def __init__( # prepare embedding model if needed if isinstance(embedding_model, str): - self.embedding_model = load_model_by_name(embedding_model) + self.embedding_model = load_model_by_id(embedding_model) else: self.embedding_model = embedding_model diff --git a/src/agentscope/models/__init__.py b/src/agentscope/models/__init__.py index 847854026..f3bcb0a75 100644 --- a/src/agentscope/models/__init__.py +++ b/src/agentscope/models/__init__.py @@ -5,8 +5,12 @@ from loguru import logger +from .config import ModelConfig from .model import ModelWrapperBase, ModelResponse -from .post_model import PostAPIModelWrapperBase +from .post_model import ( + PostAPIModelWrapperBase, + PostAPIChatWrapper, +) from .openai_model import ( OpenAIWrapper, OpenAIChatWrapper, @@ -23,20 +27,19 @@ "OpenAIChatWrapper", "OpenAIDALLEWrapper", "OpenAIEmbeddingWrapper", - "load_model_by_name", + "load_model_by_id", "read_model_configs", "clear_model_configs", ] -from ..configs.model_config import OpenAICfg, PostApiCfg - +_MODEL_CONFIGS: dict[str, dict] = {} -_MODEL_CONFIGS = [] _MODEL_MAP: dict[str, Type[ModelWrapperBase]] = { "openai": OpenAIChatWrapper, "openai_dall_e": OpenAIDALLEWrapper, "openai_embedding": OpenAIEmbeddingWrapper, "post_api": PostAPIModelWrapperBase, + "post_api_chat": PostAPIChatWrapper, } @@ -63,7 +66,7 @@ def get_model(model_type: str) -> Type[ModelWrapperBase]: return PostAPIModelWrapperBase -def load_model_by_name(model_name: str) -> ModelWrapperBase: +def load_model_by_id(model_id: str) -> ModelWrapperBase: """Load the model by name.""" if len(_MODEL_CONFIGS) == 0: raise ValueError( @@ -72,30 +75,29 @@ def load_model_by_name(model_name: str) -> ModelWrapperBase: ) # Find model config by name - config = None - for _ in _MODEL_CONFIGS: - if _["name"] == model_name: - config = {**_} - break + if model_id not in _MODEL_CONFIGS: + raise ValueError( + f"Cannot find [{model_id}] in loaded configurations.", + ) + config = _MODEL_CONFIGS[model_id] if config is None: raise ValueError( - f"Cannot find [{model_name}] in loaded configurations.", + f"Cannot find [{model_id}] in loaded configurations.", ) - model_type = config.pop("type") + model_type = config.model_type return get_model(model_type=model_type)(**config) def clear_model_configs() -> None: """Clear the loaded model configs.""" - global _MODEL_CONFIGS - _MODEL_CONFIGS = [] + _MODEL_CONFIGS.clear() def read_model_configs( configs: Union[dict, str, list], - empty_first: bool = False, + clear_existing: bool = False, ) -> None: """read model configs from a path or a list of dicts. @@ -103,14 +105,14 @@ def read_model_configs( configs (`Union[str, list, dict]`): The path of the model configs | a config dict | a list of model configs. - empty_first (`bool`, defaults to `False`): + clear_existing (`bool`, defaults to `False`): Whether to clear the loaded model configs before reading. Returns: `dict`: The model configs. """ - if empty_first: + if clear_existing: clear_model_configs() if isinstance(configs, str): @@ -127,40 +129,16 @@ def read_model_configs( ) cfgs = configs - # Checking - format_configs: list[Union[OpenAICfg, PostApiCfg]] = [] - for cfg in cfgs: - if "type" not in cfg: - raise ValueError( - f"Cannot find `type` in model config: {cfg}, " - f'whose value should be choice from ["openai", ' - f'"post_api"]', - ) - - if cfg["type"] == "openai": - openai_cfg = OpenAICfg() - openai_cfg.init(**cfg) - format_configs += [openai_cfg] - - elif cfg["type"] == "post_api": - post_api_cfg = PostApiCfg() - post_api_cfg.init(**cfg) - format_configs += [post_api_cfg] - - else: - raise ValueError( - f"Unknown model type: {cfg['type']}, please " - f"choice from ['openai', 'post_api']]", - ) + format_configs = ModelConfig.format_configs(configs=cfgs) # check if name is unique - global _MODEL_CONFIGS for cfg in format_configs: - if cfg["name"] in [_["name"] for _ in _MODEL_CONFIGS]: - raise ValueError(f'Model name "{cfg.name}" already exists.') - - _MODEL_CONFIGS.append(cfg) + if cfg.model_id in _MODEL_CONFIGS: + raise ValueError(f"model_id [{cfg.model_id}] already exists.") + _MODEL_CONFIGS[cfg.model_id] = cfg # print the loaded model configs - model_names = [_["name"] for _ in _MODEL_CONFIGS] - logger.info("Load configs for model: {}", ", ".join(model_names)) + logger.info( + "Load configs for model wrapper: {}", + ", ".join(_MODEL_CONFIGS.keys()), + ) diff --git a/src/agentscope/models/config.py b/src/agentscope/models/config.py new file mode 100644 index 000000000..4b76a4459 --- /dev/null +++ b/src/agentscope/models/config.py @@ -0,0 +1,59 @@ +# -*- coding: utf-8 -*- +"""The model config.""" +from typing import Union, Sequence, Any + +from loguru import logger + + +class ModelConfig(dict): + """Base class for model config.""" + + __getattr__ = dict.__getitem__ + __setattr__ = dict.__setitem__ + + def __init__( + self, + model_id: str, + model_type: str = None, + **kwargs: Any, + ): + """Initialize the config with the given arguments, and checking the + type of the arguments. + + Args: + model_id (`str`): The id of the generated model wrapper. + model_type (`str`, optional): The class name (or its alias) of + the generated model wrapper. Defaults to None. + + Raises: + `ValueError`: If `model_id` is not provided. + """ + if model_id is None: + raise ValueError("The `model_id` field is required for Cfg") + if model_type is None: + logger.warning( + f"`model_type` is not provided in config [{model_id}]," + " use `PostAPIModelWrapperBase` by default.", + ) + super().__init__( + model_id=model_id, + model_type=model_type, + **kwargs, + ) + + @classmethod + def format_configs( + cls, + configs: Union[Sequence[dict], dict], + ) -> Sequence: + """Covert config dicts into a list of ModelConfig. + + Args: + configs (Union[Sequence[dict], dict]): configs in dict format. + + Returns: + Sequence[ModelConfig]: converted ModelConfig list. + """ + if isinstance(configs, dict): + return [ModelConfig(**configs)] + return [ModelConfig(**cfg) for cfg in configs] diff --git a/src/agentscope/models/model.py b/src/agentscope/models/model.py index b6d15760b..e78bfd7e9 100644 --- a/src/agentscope/models/model.py +++ b/src/agentscope/models/model.py @@ -5,12 +5,12 @@ .. code-block:: python { - "type": "openai" | "post_api", - "name": "{model_name}", + "model_id": "{model_id}", + "model_type": "openai" | "post_api", ... } -After that, you can specify model by {model_name}. +After that, you can specify model by {model_id}. Note: The parameters for different types of models are different. For OpenAI API, @@ -19,9 +19,9 @@ .. code-block:: python { - "type": "openai", - "name": "{name of your model}", - "model_name": "{model_name_for_openai, e.g. gpt-3.5-turbo}", + "model_id": "{id of your model}", + "model_type": "openai", + "model": "{model_name_for_openai, e.g. gpt-3.5-turbo}", "api_key": "{your_api_key}", "organization": "{your_organization, if needed}", "client_args": { @@ -39,8 +39,8 @@ .. code-block:: python { - "type": "post_api", - "name": "{model_name}", + "model_id": "{model_id}", + "model_type": "post_api", "api_url": "{api_url}", "headers": {"Authorization": "Bearer {API_TOKEN}"}, "max_length": {max_length_of_model}, @@ -58,6 +58,7 @@ from abc import ABCMeta from functools import wraps from typing import Sequence, Any, Callable +import json from loguru import logger @@ -67,6 +68,46 @@ from ..constants import _DEFAULT_RETRY_INTERVAL +class ModelResponse: + """Encapsulation of data returned by the model. + + The main purpose of this class is to align the return formats of different + models and act as a bridge between models and agents. + """ + + def __init__( + self, + text: str = None, + embedding: Sequence = None, + image_urls: Sequence[str] = None, + raw: dict = None, + ) -> None: + self._text = text + self._embedding = embedding + self._image_urls = image_urls + self._raw = raw + + @property + def text(self) -> str: + """Text field.""" + return self._text + + @property + def embedding(self) -> Sequence: + """Embedding field.""" + return self._embedding + + @property + def image_urls(self) -> Sequence[str]: + """Image URLs field.""" + return self._image_urls + + @property + def raw(self) -> dict: + """Raw dictionary field.""" + return self._raw + + def _response_parse_decorator( model_call: Callable, ) -> Callable: @@ -154,64 +195,25 @@ def __init__(cls, name: Any, bases: Any, attrs: Any) -> None: super().__init__(name, bases, attrs) -class ModelResponse: - """Encapsulation of data returned by the model. - - The main purpose of this class is to align the return formats of different - models and act as a bridge between models and agents. - """ - - def __init__( - self, - text: str = None, - embedding: Sequence = None, - image_urls: Sequence[str] = None, - raw: dict = None, - ) -> None: - self._text = text - self._embedding = embedding - self._image_urls = image_urls - self._raw = raw - - @property - def text(self) -> str: - """Text field.""" - return self._text - - @property - def embedding(self) -> Sequence: - """Embedding field.""" - return self._embedding - - @property - def image_urls(self) -> Sequence[str]: - """Image URLs field.""" - return self._image_urls - - @property - def raw(self) -> dict: - """Raw dictionary field.""" - return self._raw - - class ModelWrapperBase(metaclass=_ModelWrapperMeta): """The base class for model wrapper.""" - def __init__( - self, - name: str, - ) -> None: - r"""Base class for model wrapper. + def __init__(self, model_id: str, **kwargs: Any) -> None: + """Base class for model wrapper. All model wrappers should inherit this class and implement the `__call__` function. Args: - name (`str`): - The name of the model, which is used to extract configuration + model_id (`str`): + The id of the model, which is used to extract configuration from the config file. """ - self.name = name + self.model_id = model_id + logger.info( + f"Initialize model [{model_id}] with config:\n" + f"{json.dumps(kwargs, indent=2)}", + ) def __call__(self, *args: Any, **kwargs: Any) -> ModelResponse: """Processing input with the model.""" diff --git a/src/agentscope/models/openai_model.py b/src/agentscope/models/openai_model.py index 6e347d67c..c78b26d2b 100644 --- a/src/agentscope/models/openai_model.py +++ b/src/agentscope/models/openai_model.py @@ -16,6 +16,7 @@ from ..utils.monitor import get_full_name from ..utils import QuotaExceededError from ..utils.token_utils import get_openai_max_length +from ..constants import _DEFAULT_API_BUDGET class OpenAIWrapper(ModelWrapperBase): @@ -23,23 +24,23 @@ class OpenAIWrapper(ModelWrapperBase): def __init__( self, - name: str, - model_name: str = None, + model_id: str, + model: str = None, api_key: str = None, organization: str = None, client_args: dict = None, generate_args: dict = None, - budget: float = None, + budget: float = _DEFAULT_API_BUDGET, + **kwargs: Any, ) -> None: """Initialize the openai client. Args: - name (`str`): - The name of the model wrapper, which is used to identify + model_id (`str`): + The id of the model wrapper, which is used to identify model configs. - model_name (`str`, default `None`): - The name of the model to use in OpenAI API. If not - specified, it will be the same as `name`. + model (`str`, default `None`): + The name of the model to use in OpenAI API. api_key (`str`, default `None`): The API key for OpenAI API. If not specified, it will be read from the environment variable `OPENAI_API_KEY`. @@ -55,14 +56,23 @@ def __init__( The total budget using this model. Set to `None` means no limit. """ - super().__init__(name) + if model is None: + model = model_id + super().__init__( + model_id=model_id, + model=model, + client_args=client_args, + generate_args=generate_args, + budget=budget, + **kwargs, + ) if openai is None: raise ImportError( "Cannot find openai package in current python environment.", ) - self.model_name = model_name or name + self.model = model self.generate_args = generate_args or {} self.client = openai.OpenAI( @@ -73,10 +83,10 @@ def __init__( # Set the max length of OpenAI model try: - self.max_length = get_openai_max_length(self.model_name) + self.max_length = get_openai_max_length(self.model) except Exception as e: logger.warning( - f"fail to get max_length for {self.model_name}: " f"{e}", + f"fail to get max_length for {self.model}: " f"{e}", ) self.max_length = None @@ -89,9 +99,9 @@ def __init__( def _register_budget(self) -> None: self.monitor = MonitorFactory.get_monitor() self.monitor.register_budget( - model_name=self.model_name, + model_name=self.model, value=self.budget, - prefix=self.model_name, + prefix=self.model, ) def _register_default_metrics(self) -> None: @@ -110,7 +120,7 @@ def _metric(self, metric_name: str) -> str: Returns: `str`: Metric name of this wrapper. """ - return get_full_name(name=metric_name, prefix=self.model_name) + return get_full_name(name=metric_name, prefix=self.model) class OpenAIChatWrapper(OpenAIWrapper): @@ -191,7 +201,7 @@ def __call__( # step3: forward to generate response response = self.client.chat.completions.create( - model=self.model_name, + model=self.model, messages=messages, **kwargs, ) @@ -199,7 +209,7 @@ def __call__( # step4: record the api invocation if needed self._save_model_invocation( arguments={ - "model": self.model_name, + "model": self.model, "messages": messages, **kwargs, }, @@ -210,7 +220,7 @@ def __call__( try: self.monitor.update( response.usage.model_dump(), - prefix=self.model_name, + prefix=self.model, ) except QuotaExceededError as e: # TODO: optimize quota exceeded error handling process @@ -291,7 +301,7 @@ def __call__( # step2: forward to generate response try: response = self.client.images.generate( - model=self.model_name, + model=self.model, prompt=prompt, **kwargs, ) @@ -304,7 +314,7 @@ def __call__( # step3: record the model api invocation if needed self._save_model_invocation( arguments={ - "model": self.model_name, + "model": self.model, "prompt": prompt, **kwargs, }, @@ -383,14 +393,14 @@ def __call__( # step2: forward to generate response response = self.client.embeddings.create( input=texts, - model=self.model_name, + model=self.model, **kwargs, ) # step3: record the model api invocation if needed self._save_model_invocation( arguments={ - "model": self.model_name, + "model": self.model, "input": texts, **kwargs, }, diff --git a/src/agentscope/models/post_model.py b/src/agentscope/models/post_model.py index 8c6a3eab3..f345fb28f 100644 --- a/src/agentscope/models/post_model.py +++ b/src/agentscope/models/post_model.py @@ -18,7 +18,7 @@ class PostAPIModelWrapperBase(ModelWrapperBase): def __init__( self, - name: str, + model_id: str, api_url: str, headers: dict = None, max_length: int = 2048, @@ -28,12 +28,13 @@ def __init__( max_retries: int = _DEFAULT_MAX_RETRIES, messages_key: str = _DEFAULT_MESSAGES_KEY, retry_interval: int = _DEFAULT_RETRY_INTERVAL, + **kwargs: Any, ) -> None: """Initialize the model wrapper. Args: - name (`str`): - The name of the model. + model_id (`str`): + The id of the model. api_url (`str`): The url of the post request api. headers (`dict`, defaults to `None`): @@ -70,8 +71,19 @@ def __init__( **post_args ) """ - super().__init__(name) - + super().__init__( + model_id=model_id, + api_url=api_url, + headers=headers, + max_length=max_length, + timeout=timeout, + json_args=json_args, + post_args=post_args, + max_retries=max_retries, + messages_key=messages_key, + retry_interval=retry_interval, + **kwargs, + ) self.api_url = api_url self.headers = headers self.max_length = max_length @@ -157,33 +169,8 @@ def __call__(self, input_: str, **kwargs: Any) -> ModelResponse: class PostAPIChatWrapper(PostAPIModelWrapperBase): - """A post api model wrapper compatilble with openai chat""" - - def __init__( - self, - name: str, - api_url: str, - headers: dict = None, - max_length: int = 2048, - timeout: int = 30, - json_args: dict = None, - post_args: dict = None, - max_retries: int = _DEFAULT_MAX_RETRIES, - messages_key: str = _DEFAULT_MESSAGES_KEY, - retry_interval: int = _DEFAULT_RETRY_INTERVAL, - ) -> None: - super().__init__( - name=name, - api_url=api_url, - headers=headers, - max_length=max_length, - timeout=timeout, - json_args=json_args, - post_args=post_args, - max_retries=max_retries, - messages_key=messages_key, - retry_interval=retry_interval, - ) + """A post api model wrapper compatilble with openai chat, e.g., vLLM, + FastChat.""" def _parse_response(self, response: dict) -> ModelResponse: return ModelResponse( diff --git a/src/agentscope/service/text_processing/summarization.py b/src/agentscope/service/text_processing/summarization.py index e6b61830b..17a4824c4 100644 --- a/src/agentscope/service/text_processing/summarization.py +++ b/src/agentscope/service/text_processing/summarization.py @@ -98,7 +98,7 @@ def summarization( except ValueError: return ServiceResponse( ServiceExecStatus.ERROR, - content=f"Summarization by model {model.model_name} fail", + content=f"Summarization by model {model.model} fail", ) else: try: diff --git a/tests/prompt_engine_test.py b/tests/prompt_engine_test.py index 388806c4c..85b8d5123 100644 --- a/tests/prompt_engine_test.py +++ b/tests/prompt_engine_test.py @@ -4,7 +4,7 @@ from typing import Any from agentscope.models import read_model_configs -from agentscope.models import load_model_by_name +from agentscope.models import load_model_by_id from agentscope.models import ModelResponse, OpenAIWrapper from agentscope.prompt import PromptEngine @@ -29,8 +29,8 @@ def setUp(self) -> None: read_model_configs( [ { - "type": "post_api", - "name": "open-source", + "model_type": "post_api", + "model_id": "open-source", "api_url": "http://xxx", "headers": {"Autherization": "Bearer {API_TOKEN}"}, "parameters": { @@ -38,15 +38,14 @@ def setUp(self) -> None: }, }, { - "type": "openai", - "name": "gpt-4", - "parameters": { - "api_key": "xxx", - "organization_id": "xxx", - }, + "model_type": "openai", + "model_id": "gpt-4", + "model": "gpt-4", + "api_key": "xxx", + "organization": "xxx", }, ], - empty_first=True, + clear_existing=True, ) def test_list_prompt(self) -> None: @@ -110,7 +109,7 @@ def _register_default_metrics(self) -> None: def test_str_prompt(self) -> None: """Test for string prompt.""" - model = load_model_by_name("open-source") + model = load_model_by_id("open-source") engine = PromptEngine(model) prompt = engine.join( diff --git a/tests/record_api_invocation_test.py b/tests/record_api_invocation_test.py index 3711e0b65..ac2186562 100644 --- a/tests/record_api_invocation_test.py +++ b/tests/record_api_invocation_test.py @@ -43,7 +43,7 @@ def test_record_model_invocation_with_init( # test agentscope.init(save_api_invoke=True) model = OpenAIChatWrapper( - name="gpt-4", + model_id="gpt-4", api_key="xxx", organization="xxx", )