diff --git a/.github/workflows/fulltest.yaml b/.github/workflows/fulltest.yaml index 2ab6444fa..32eb3da00 100644 --- a/.github/workflows/fulltest.yaml +++ b/.github/workflows/fulltest.yaml @@ -79,8 +79,8 @@ jobs: ./tests/data/rsp_cache_new.json retention-days: 3 if: ${{ always() }} - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - if: ${{ always() }} + # - name: Upload coverage reports to Codecov + # uses: codecov/codecov-action@v3 + # env: + # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + # if: ${{ always() }} diff --git a/.github/workflows/unittest.yaml b/.github/workflows/unittest.yaml index 25f82b1e6..1fd193b52 100644 --- a/.github/workflows/unittest.yaml +++ b/.github/workflows/unittest.yaml @@ -91,8 +91,8 @@ jobs: ./tests/data/rsp_cache_new.json retention-days: 3 if: ${{ always() }} - - name: Upload coverage reports to Codecov - uses: codecov/codecov-action@v3 - env: - CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} - if: ${{ always() }} + # - name: Upload coverage reports to Codecov + # uses: codecov/codecov-action@v3 + # env: + # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} + # if: ${{ always() }} diff --git a/README.md b/README.md index aa7bef194..792a020b5 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ # MetaGPT: The Multi-Agent Framework

-MetaGPT logo: Enable GPT to work in software company, collaborating to tackle more complex tasks. +MetaGPT logo: Enable GPT to work in a software company, collaborating to tackle more complex tasks.

@@ -23,11 +23,11 @@ # MetaGPT: The Multi-Agent Framework

Open in Dev Containers Open in GitHub Codespaces - Hugging Face + Hugging Face

## News -🚀 Mar. 29, 2024: [v0.8.0](https://github.com/geekan/MetaGPT/releases/tag/v0.8.0) released. Now you can use Data Interpreter ([arxiv](https://arxiv.org/abs/2402.18679), [example](https://docs.deepwisdom.ai/main/en/DataInterpreter/), [code](https://github.com/geekan/MetaGPT/tree/main/examples/di)) via pypi package import. Meanwhile, we integrated RAG module and supported multiple new LLMs. +🚀 Mar. 29, 2024: [v0.8.0](https://github.com/geekan/MetaGPT/releases/tag/v0.8.0) released. Now you can use Data Interpreter ([arxiv](https://arxiv.org/abs/2402.18679), [example](https://docs.deepwisdom.ai/main/en/DataInterpreter/), [code](https://github.com/geekan/MetaGPT/tree/main/examples/di)) via pypi package import. Meanwhile, we integrated the RAG module and supported multiple new LLMs. 🚀 Feb. 08, 2024: [v0.7.0](https://github.com/geekan/MetaGPT/releases/tag/v0.7.0) released, supporting assigning different LLMs to different Roles. We also introduced [Data Interpreter](https://github.com/geekan/MetaGPT/blob/main/examples/di/README.md), a powerful agent capable of solving a wide range of real-world problems. @@ -121,7 +121,7 @@ ### Usage ### QuickStart & Demo Video -- Try it on [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT) +- Try it on [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT-SoftwareCompany) - [Matthew Berman: How To Install MetaGPT - Build A Startup With One Prompt!!](https://youtu.be/uT75J_KG_aY) - [Official Demo Video](https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d) @@ -141,7 +141,7 @@ ## Tutorial - [Data Interpreter](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/interpreter/intro.html) - [Debate](https://docs.deepwisdom.ai/main/en/guide/use_cases/multi_agent/debate.html) - [Researcher](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/researcher.html) - - [Recepit Assistant](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/receipt_assistant.html) + - [Receipt Assistant](https://docs.deepwisdom.ai/main/en/guide/use_cases/agent/receipt_assistant.html) - ❓ [FAQs](https://docs.deepwisdom.ai/main/en/guide/faq.html) ## Support diff --git a/docs/README_CN.md b/docs/README_CN.md index 6ac75fbeb..88583cf24 100644 --- a/docs/README_CN.md +++ b/docs/README_CN.md @@ -22,7 +22,7 @@ # MetaGPT: 多智能体框架

Open in Dev Containers Open in GitHub Codespaces - Hugging Face + Hugging Face

1. MetaGPT输入**一句话的老板需求**,输出**用户故事 / 竞品分析 / 需求 / 数据结构 / APIs / 文件等** @@ -77,7 +77,7 @@ # 步骤2: 使用容器运行metagpt演示 详细的安装请参考 [docker_install](https://docs.deepwisdom.ai/main/zh/guide/get_started/installation.html#%E4%BD%BF%E7%94%A8docker%E5%AE%89%E8%A3%85) ### 快速开始的演示视频 -- 在 [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT) 上进行体验 +- 在 [MetaGPT Huggingface Space](https://huggingface.co/spaces/deepwisdom/MetaGPT-SoftwareCompany) 上进行体验 - [Matthew Berman: How To Install MetaGPT - Build A Startup With One Prompt!!](https://youtu.be/uT75J_KG_aY) - [官方演示视频](https://github.com/geekan/MetaGPT/assets/2707039/5e8c1062-8c35-440f-bb20-2b0320f8d27d) diff --git a/docs/README_JA.md b/docs/README_JA.md index 91f85830c..fd96602b5 100644 --- a/docs/README_JA.md +++ b/docs/README_JA.md @@ -22,7 +22,7 @@ # MetaGPT: マルチエージェントフレームワーク

Open in Dev Containers Open in GitHub Codespaces - Hugging Face + Hugging Face

1. MetaGPT は、**1 行の要件** を入力とし、**ユーザーストーリー / 競合分析 / 要件 / データ構造 / API / 文書など** を出力します。 @@ -292,7 +292,7 @@ ## クイックスタート - [MetaGPT クイックスタート](https://deepwisdom.feishu.cn/wiki/CyY9wdJc4iNqArku3Lncl4v8n2b) Hugging Face Space で試す -- https://huggingface.co/spaces/deepwisdom/MetaGPT +- https://huggingface.co/spaces/deepwisdom/MetaGPT-SoftwareCompany ## 引用 diff --git a/metagpt/configs/llm_config.py b/metagpt/configs/llm_config.py index 7388063aa..60118303c 100644 --- a/metagpt/configs/llm_config.py +++ b/metagpt/configs/llm_config.py @@ -5,6 +5,7 @@ @Author : alexanderwu @File : llm_config.py """ + from enum import Enum from typing import Optional @@ -57,6 +58,7 @@ class LLMConfig(YamlModel): # For Cloud Service Provider like Baidu/ Alibaba access_key: Optional[str] = None secret_key: Optional[str] = None + session_token: Optional[str] = None endpoint: Optional[str] = None # for self-deployed model on the cloud # For Spark(Xunfei), maybe remove later @@ -76,6 +78,7 @@ class LLMConfig(YamlModel): best_of: Optional[int] = None n: Optional[int] = None stream: bool = True + seed: Optional[int] = None # https://cookbook.openai.com/examples/using_logprobs logprobs: Optional[bool] = None top_logprobs: Optional[int] = None diff --git a/metagpt/document_store/milvus_store.py b/metagpt/document_store/milvus_store.py new file mode 100644 index 000000000..e4d6d985e --- /dev/null +++ b/metagpt/document_store/milvus_store.py @@ -0,0 +1,99 @@ +from dataclasses import dataclass +from typing import Any, Dict, List, Optional + +from metagpt.document_store.base_store import BaseStore + + +@dataclass +class MilvusConnection: + """ + Args: + uri: milvus url + token: milvus token + """ + + uri: str = None + token: str = None + + +class MilvusStore(BaseStore): + def __init__(self, connect: MilvusConnection): + try: + from pymilvus import MilvusClient + except ImportError: + raise Exception("Please install pymilvus first.") + if not connect.uri: + raise Exception("please check MilvusConnection, uri must be set.") + self.client = MilvusClient(uri=connect.uri, token=connect.token) + + def create_collection(self, collection_name: str, dim: int, enable_dynamic_schema: bool = True): + from pymilvus import DataType + + if self.client.has_collection(collection_name=collection_name): + self.client.drop_collection(collection_name=collection_name) + + schema = self.client.create_schema( + auto_id=False, + enable_dynamic_field=False, + ) + schema.add_field(field_name="id", datatype=DataType.VARCHAR, is_primary=True, max_length=36) + schema.add_field(field_name="vector", datatype=DataType.FLOAT_VECTOR, dim=dim) + + index_params = self.client.prepare_index_params() + index_params.add_index(field_name="vector", index_type="AUTOINDEX", metric_type="COSINE") + + self.client.create_collection( + collection_name=collection_name, + schema=schema, + index_params=index_params, + enable_dynamic_schema=enable_dynamic_schema, + ) + + @staticmethod + def build_filter(key, value) -> str: + if isinstance(value, str): + filter_expression = f'{key} == "{value}"' + else: + if isinstance(value, list): + filter_expression = f"{key} in {value}" + else: + filter_expression = f"{key} == {value}" + + return filter_expression + + def search( + self, + collection_name: str, + query: List[float], + filter: Dict = None, + limit: int = 10, + output_fields: Optional[List[str]] = None, + ) -> List[dict]: + filter_expression = " and ".join([self.build_filter(key, value) for key, value in filter.items()]) + print(filter_expression) + + res = self.client.search( + collection_name=collection_name, + data=[query], + filter=filter_expression, + limit=limit, + output_fields=output_fields, + )[0] + + return res + + def add(self, collection_name: str, _ids: List[str], vector: List[List[float]], metadata: List[Dict[str, Any]]): + data = dict() + + for i, id in enumerate(_ids): + data["id"] = id + data["vector"] = vector[i] + data["metadata"] = metadata[i] + + self.client.upsert(collection_name=collection_name, data=data) + + def delete(self, collection_name: str, _ids: List[str]): + self.client.delete(collection_name=collection_name, ids=_ids) + + def write(self, *args, **kwargs): + pass diff --git a/metagpt/provider/bedrock/bedrock_provider.py b/metagpt/provider/bedrock/bedrock_provider.py index 1236bf56b..c5e3b7bd2 100644 --- a/metagpt/provider/bedrock/bedrock_provider.py +++ b/metagpt/provider/bedrock/bedrock_provider.py @@ -57,15 +57,34 @@ class AnthropicProvider(BaseBedrockProvider): class CohereProvider(BaseBedrockProvider): - # See https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command.html + # For more information, see + # (Command) https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command.html + # (Command R/R+) https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html + + def __init__(self, model_name: str) -> None: + self.model_name = model_name def _get_completion_from_dict(self, rsp_dict: dict) -> str: return rsp_dict["generations"][0]["text"] + def messages_to_prompt(self, messages: list[dict]) -> str: + if "command-r" in self.model_name: + role_map = {"user": "USER", "assistant": "CHATBOT", "system": "USER"} + messages = list( + map(lambda message: {"role": role_map[message["role"]], "message": message["content"]}, messages) + ) + return messages + else: + """[{"role": "user", "content": msg}] to user: etc.""" + return "\n".join([f"{msg['role']}: {msg['content']}" for msg in messages]) + def get_request_body(self, messages: list[dict], generate_kwargs, *args, **kwargs): - body = json.dumps( - {"prompt": self.messages_to_prompt(messages), "stream": kwargs.get("stream", False), **generate_kwargs} - ) + prompt = self.messages_to_prompt(messages) + if "command-r" in self.model_name: + chat_history, message = prompt[:-1], prompt[-1]["message"] + body = json.dumps({"message": message, "chat_history": chat_history, **generate_kwargs}) + else: + body = json.dumps({"prompt": prompt, "stream": kwargs.get("stream", False), **generate_kwargs}) return body def get_choice_text_from_stream(self, event) -> str: @@ -95,10 +114,37 @@ class MetaProvider(BaseBedrockProvider): class Ai21Provider(BaseBedrockProvider): # See https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jurassic2.html - max_tokens_field_name = "maxTokens" + def __init__(self, model_type: Literal["j2", "jamba"]) -> None: + self.model_type = model_type + if self.model_type == "j2": + self.max_tokens_field_name = "maxTokens" + else: + self.max_tokens_field_name = "max_tokens" + + def get_request_body(self, messages: list[dict], generate_kwargs, *args, **kwargs) -> str: + if self.model_type == "j2": + body = super().get_request_body(messages, generate_kwargs, *args, **kwargs) + else: + body = json.dumps( + { + "messages": messages, + **generate_kwargs, + } + ) + return body + + def get_choice_text_from_stream(self, event) -> str: + rsp_dict = json.loads(event["chunk"]["bytes"]) + completions = rsp_dict.get("choices", [{}])[0].get("delta", {}).get("content", "") + return completions def _get_completion_from_dict(self, rsp_dict: dict) -> str: - return rsp_dict["completions"][0]["data"]["text"] + if self.model_type == "j2": + # See https://docs.ai21.com/reference/j2-complete-ref + return rsp_dict["completions"][0]["data"]["text"] + else: + # See https://docs.ai21.com/reference/jamba-instruct-api + return rsp_dict["choices"][0]["message"]["content"] class AmazonProvider(BaseBedrockProvider): @@ -136,4 +182,10 @@ def get_provider(model_id: str): if provider == "meta": # distinguish llama2 and llama3 return PROVIDERS[provider](model_name[:6]) + elif provider == "ai21": + # distinguish between j2 and jamba + return PROVIDERS[provider](model_name.split("-")[0]) + elif provider == "cohere": + # distinguish between R/R+ and older models + return PROVIDERS[provider](model_name) return PROVIDERS[provider]() diff --git a/metagpt/provider/bedrock/utils.py b/metagpt/provider/bedrock/utils.py index 46520d1d5..e67796362 100644 --- a/metagpt/provider/bedrock/utils.py +++ b/metagpt/provider/bedrock/utils.py @@ -1,52 +1,97 @@ from metagpt.logs import logger # max_tokens for each model -NOT_SUUPORT_STREAM_MODELS = { - "ai21.j2-grande-instruct": 8000, - "ai21.j2-jumbo-instruct": 8000, - "ai21.j2-mid": 8000, - "ai21.j2-mid-v1": 8000, - "ai21.j2-ultra": 8000, - "ai21.j2-ultra-v1": 8000, +NOT_SUPPORT_STREAM_MODELS = { + # Jurassic-2 Mid-v1 and Ultra-v1 + # + Legacy date: 2024-04-30 (us-west-2/Oregon) + # + EOL date: 2024-08-31 (us-west-2/Oregon) + "ai21.j2-mid-v1": 8191, + "ai21.j2-ultra-v1": 8191, } SUPPORT_STREAM_MODELS = { - "amazon.titan-tg1-large": 8000, - "amazon.titan-text-express-v1": 8000, - "amazon.titan-text-express-v1:0:8k": 8000, - "amazon.titan-text-lite-v1:0:4k": 4000, - "amazon.titan-text-lite-v1": 4000, - "anthropic.claude-instant-v1": 100000, - "anthropic.claude-instant-v1:2:100k": 100000, - "anthropic.claude-v1": 100000, - "anthropic.claude-v2": 100000, - "anthropic.claude-v2:1": 200000, - "anthropic.claude-v2:0:18k": 18000, - "anthropic.claude-v2:1:200k": 200000, - "anthropic.claude-3-sonnet-20240229-v1:0": 200000, - "anthropic.claude-3-sonnet-20240229-v1:0:28k": 28000, - "anthropic.claude-3-sonnet-20240229-v1:0:200k": 200000, - "anthropic.claude-3-haiku-20240307-v1:0": 200000, - "anthropic.claude-3-5-sonnet-20240620-v1:0": 200000, - "anthropic.claude-3-haiku-20240307-v1:0:48k": 48000, - "anthropic.claude-3-haiku-20240307-v1:0:200k": 200000, - # currently (2024-4-29) only available at US West (Oregon) AWS Region. - "anthropic.claude-3-opus-20240229-v1:0": 200000, - "cohere.command-text-v14": 4000, - "cohere.command-text-v14:7:4k": 4000, - "cohere.command-light-text-v14": 4000, - "cohere.command-light-text-v14:7:4k": 4000, - "meta.llama2-13b-chat-v1:0:4k": 4000, - "meta.llama2-13b-chat-v1": 2000, - "meta.llama2-70b-v1": 4000, - "meta.llama2-70b-v1:0:4k": 4000, - "meta.llama2-70b-chat-v1": 2000, - "meta.llama2-70b-chat-v1:0:4k": 2000, - "meta.llama3-8b-instruct-v1:0": 2000, - "meta.llama3-70b-instruct-v1:0": 2000, - "mistral.mistral-7b-instruct-v0:2": 32000, - "mistral.mixtral-8x7b-instruct-v0:1": 32000, - "mistral.mistral-large-2402-v1:0": 32000, + # Jamba-Instruct + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-jamba.html + "ai21.jamba-instruct-v1:0": 4096, + # Titan Text G1 - Lite + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-titan-text.html + "amazon.titan-text-lite-v1:0:4k": 4096, + "amazon.titan-text-lite-v1": 4096, + # Titan Text G1 - Express + "amazon.titan-text-express-v1": 8192, + "amazon.titan-text-express-v1:0:8k": 8192, + # Titan Text Premier + "amazon.titan-text-premier-v1:0": 3072, + "amazon.titan-text-premier-v1:0:32k": 3072, + # Claude Instant v1 + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-anthropic-claude-text-completion.html + # https://docs.anthropic.com/en/docs/about-claude/models#model-comparison + "anthropic.claude-instant-v1": 4096, + "anthropic.claude-instant-v1:2:100k": 4096, + # Claude v2 + "anthropic.claude-v2": 4096, + "anthropic.claude-v2:0:18k": 4096, + "anthropic.claude-v2:0:100k": 4096, + # Claude v2.1 + "anthropic.claude-v2:1": 4096, + "anthropic.claude-v2:1:18k": 4096, + "anthropic.claude-v2:1:200k": 4096, + # Claude 3 Sonnet + "anthropic.claude-3-sonnet-20240229-v1:0": 4096, + "anthropic.claude-3-sonnet-20240229-v1:0:28k": 4096, + "anthropic.claude-3-sonnet-20240229-v1:0:200k": 4096, + # Claude 3 Haiku + "anthropic.claude-3-haiku-20240307-v1:0": 4096, + "anthropic.claude-3-haiku-20240307-v1:0:48k": 4096, + "anthropic.claude-3-haiku-20240307-v1:0:200k": 4096, + # Claude 3 Opus + "anthropic.claude-3-opus-20240229-v1:0": 4096, + # Claude 3.5 Sonnet + "anthropic.claude-3-5-sonnet-20240620-v1:0": 8192, + # Command Text + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command.html + "cohere.command-text-v14": 4096, + "cohere.command-text-v14:7:4k": 4096, + # Command Light Text + "cohere.command-light-text-v14": 4096, + "cohere.command-light-text-v14:7:4k": 4096, + # Command R + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-cohere-command-r-plus.html + "cohere.command-r-v1:0": 4096, + # Command R+ + "cohere.command-r-plus-v1:0": 4096, + # Llama 2 (--> Llama 3/3.1/3.2) !!! + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-meta.html + # + Legacy: 2024-05-12 + # + EOL: 2024-10-30 + # "meta.llama2-13b-chat-v1": 2048, + # "meta.llama2-13b-chat-v1:0:4k": 2048, + # "meta.llama2-70b-v1": 2048, + # "meta.llama2-70b-v1:0:4k": 2048, + # "meta.llama2-70b-chat-v1": 2048, + # "meta.llama2-70b-chat-v1:0:4k": 2048, + # Llama 3 Instruct + # "meta.llama3-8b-instruct-v1:0": 2048, + "meta.llama3-70b-instruct-v1:0": 2048, + # Llama 3.1 Instruct + # "meta.llama3-1-8b-instruct-v1:0": 2048, + "meta.llama3-1-70b-instruct-v1:0": 2048, + "meta.llama3-1-405b-instruct-v1:0": 2048, + # Llama 3.2 Instruct + # "meta.llama3-2-3b-instruct-v1:0": 2048, + # "meta.llama3-2-11b-instruct-v1:0": 2048, + "meta.llama3-2-90b-instruct-v1:0": 2048, + # Mistral 7B Instruct + # https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-mistral-text-completion.html + # "mistral.mistral-7b-instruct-v0:2": 8192, + # Mixtral 8x7B Instruct + "mistral.mixtral-8x7b-instruct-v0:1": 4096, + # Mistral Small + "mistral.mistral-small-2402-v1:0": 8192, + # Mistral Large (24.02) + "mistral.mistral-large-2402-v1:0": 8192, + # Mistral Large 2 (24.07) + "mistral.mistral-large-2407-v1:0": 8192, } # TODO:use a more general function for constructing chat templates. @@ -106,7 +151,7 @@ def messages_to_prompt_claude2(messages: list[dict]) -> str: def get_max_tokens(model_id: str) -> int: try: - max_tokens = (NOT_SUUPORT_STREAM_MODELS | SUPPORT_STREAM_MODELS)[model_id] + max_tokens = (NOT_SUPPORT_STREAM_MODELS | SUPPORT_STREAM_MODELS)[model_id] except KeyError: logger.warning(f"Couldn't find model:{model_id} , max tokens has been set to 2048") max_tokens = 2048 diff --git a/metagpt/provider/bedrock_api.py b/metagpt/provider/bedrock_api.py index 03954e5c2..4e783f579 100644 --- a/metagpt/provider/bedrock_api.py +++ b/metagpt/provider/bedrock_api.py @@ -1,5 +1,6 @@ import asyncio import json +import os from functools import partial from typing import List, Literal @@ -11,7 +12,7 @@ from metagpt.const import USE_CONFIG_TIMEOUT from metagpt.logs import log_llm_stream, logger from metagpt.provider.base_llm import BaseLLM from metagpt.provider.bedrock.bedrock_provider import get_provider -from metagpt.provider.bedrock.utils import NOT_SUUPORT_STREAM_MODELS, get_max_tokens +from metagpt.provider.bedrock.utils import NOT_SUPPORT_STREAM_MODELS, get_max_tokens from metagpt.provider.llm_provider_registry import register_provider from metagpt.utils.cost_manager import CostManager from metagpt.utils.token_counter import BEDROCK_TOKEN_COSTS @@ -24,18 +25,19 @@ class BedrockLLM(BaseLLM): self.__client = self.__init_client("bedrock-runtime") self.__provider = get_provider(self.config.model) self.cost_manager = CostManager(token_costs=BEDROCK_TOKEN_COSTS) - if self.config.model in NOT_SUUPORT_STREAM_MODELS: + if self.config.model in NOT_SUPPORT_STREAM_MODELS: logger.warning(f"model {self.config.model} doesn't support streaming output!") def __init_client(self, service_name: Literal["bedrock-runtime", "bedrock"]): """initialize boto3 client""" # access key and secret key from https://us-east-1.console.aws.amazon.com/iam - self.__credentital_kwargs = { - "aws_secret_access_key": self.config.secret_key, - "aws_access_key_id": self.config.access_key, - "region_name": self.config.region_name, + self.__credential_kwargs = { + "aws_secret_access_key": os.environ.get("AWS_SECRET_ACCESS_KEY", self.config.secret_key), + "aws_access_key_id": os.environ.get("AWS_ACCESS_KEY_ID", self.config.access_key), + "aws_session_token": os.environ.get("AWS_SESSION_TOKEN", self.config.session_token), + "region_name": os.environ.get("AWS_DEFAULT_REGION", self.config.region_name), } - session = boto3.Session(**self.__credentital_kwargs) + session = boto3.Session(**self.__credential_kwargs) client = session.client(service_name) return client @@ -111,7 +113,7 @@ class BedrockLLM(BaseLLM): return await self.acompletion(messages) async def _achat_completion_stream(self, messages: list[dict], timeout=USE_CONFIG_TIMEOUT) -> str: - if self.config.model in NOT_SUUPORT_STREAM_MODELS: + if self.config.model in NOT_SUPPORT_STREAM_MODELS: rsp = await self.acompletion(messages) full_text = self.get_choice_text(rsp) log_llm_stream(full_text) diff --git a/metagpt/provider/openai_api.py b/metagpt/provider/openai_api.py index ce3a06ec8..1d2057e50 100644 --- a/metagpt/provider/openai_api.py +++ b/metagpt/provider/openai_api.py @@ -103,7 +103,7 @@ class OpenAILLM(BaseLLM): if has_finished: # for oneapi, there has a usage chunk after finish_reason not none chunk if chunk_has_usage: - usage = CompletionUsage(**chunk.usage) + usage = CompletionUsage(**chunk.usage) if isinstance(chunk.usage, dict) else chunk.usage if finish_reason: if chunk_has_usage: # Some services have usage as an attribute of the chunk, such as Fireworks diff --git a/metagpt/rag/factories/index.py b/metagpt/rag/factories/index.py index f897af3ad..4e6d6b167 100644 --- a/metagpt/rag/factories/index.py +++ b/metagpt/rag/factories/index.py @@ -8,6 +8,7 @@ from llama_index.core.vector_stores.types import BasePydanticVectorStore from llama_index.vector_stores.chroma import ChromaVectorStore from llama_index.vector_stores.elasticsearch import ElasticsearchStore from llama_index.vector_stores.faiss import FaissVectorStore +from llama_index.vector_stores.milvus import MilvusVectorStore from metagpt.rag.factories.base import ConfigBasedFactory from metagpt.rag.schema import ( @@ -17,6 +18,7 @@ from metagpt.rag.schema import ( ElasticsearchIndexConfig, ElasticsearchKeywordIndexConfig, FAISSIndexConfig, + MilvusIndexConfig, ) @@ -28,6 +30,7 @@ class RAGIndexFactory(ConfigBasedFactory): BM25IndexConfig: self._create_bm25, ElasticsearchIndexConfig: self._create_es, ElasticsearchKeywordIndexConfig: self._create_es, + MilvusIndexConfig: self._create_milvus, } super().__init__(creators) @@ -46,6 +49,11 @@ class RAGIndexFactory(ConfigBasedFactory): return self._index_from_storage(storage_context=storage_context, config=config, **kwargs) + def _create_milvus(self, config: MilvusIndexConfig, **kwargs) -> VectorStoreIndex: + vector_store = MilvusVectorStore(collection_name=config.collection_name, uri=config.uri, token=config.token) + + return self._index_from_vector_store(vector_store=vector_store, config=config, **kwargs) + def _create_chroma(self, config: ChromaIndexConfig, **kwargs) -> VectorStoreIndex: db = chromadb.PersistentClient(str(config.persist_path)) chroma_collection = db.get_or_create_collection(config.collection_name, metadata=config.metadata) diff --git a/metagpt/rag/factories/retriever.py b/metagpt/rag/factories/retriever.py index 1460e131b..490df4906 100644 --- a/metagpt/rag/factories/retriever.py +++ b/metagpt/rag/factories/retriever.py @@ -12,6 +12,7 @@ from llama_index.core.vector_stores.types import BasePydanticVectorStore from llama_index.vector_stores.chroma import ChromaVectorStore from llama_index.vector_stores.elasticsearch import ElasticsearchStore from llama_index.vector_stores.faiss import FaissVectorStore +from llama_index.vector_stores.milvus import MilvusVectorStore from metagpt.rag.factories.base import ConfigBasedFactory from metagpt.rag.retrievers.base import RAGRetriever @@ -20,6 +21,7 @@ from metagpt.rag.retrievers.chroma_retriever import ChromaRetriever from metagpt.rag.retrievers.es_retriever import ElasticsearchRetriever from metagpt.rag.retrievers.faiss_retriever import FAISSRetriever from metagpt.rag.retrievers.hybrid_retriever import SimpleHybridRetriever +from metagpt.rag.retrievers.milvus_retriever import MilvusRetriever from metagpt.rag.schema import ( BaseRetrieverConfig, BM25RetrieverConfig, @@ -27,6 +29,7 @@ from metagpt.rag.schema import ( ElasticsearchKeywordRetrieverConfig, ElasticsearchRetrieverConfig, FAISSRetrieverConfig, + MilvusRetrieverConfig, ) @@ -56,6 +59,7 @@ class RetrieverFactory(ConfigBasedFactory): ChromaRetrieverConfig: self._create_chroma_retriever, ElasticsearchRetrieverConfig: self._create_es_retriever, ElasticsearchKeywordRetrieverConfig: self._create_es_retriever, + MilvusRetrieverConfig: self._create_milvus_retriever, } super().__init__(creators) @@ -76,6 +80,11 @@ class RetrieverFactory(ConfigBasedFactory): return index.as_retriever() + def _create_milvus_retriever(self, config: MilvusRetrieverConfig, **kwargs) -> MilvusRetriever: + config.index = self._build_milvus_index(config, **kwargs) + + return MilvusRetriever(**config.model_dump()) + def _create_faiss_retriever(self, config: FAISSRetrieverConfig, **kwargs) -> FAISSRetriever: config.index = self._build_faiss_index(config, **kwargs) @@ -128,6 +137,14 @@ class RetrieverFactory(ConfigBasedFactory): return self._build_index_from_vector_store(config, vector_store, **kwargs) + @get_or_build_index + def _build_milvus_index(self, config: MilvusRetrieverConfig, **kwargs) -> VectorStoreIndex: + vector_store = MilvusVectorStore( + uri=config.uri, collection_name=config.collection_name, token=config.token, dim=config.dimensions + ) + + return self._build_index_from_vector_store(config, vector_store, **kwargs) + @get_or_build_index def _build_es_index(self, config: ElasticsearchRetrieverConfig, **kwargs) -> VectorStoreIndex: vector_store = ElasticsearchStore(**config.store_config.model_dump()) diff --git a/metagpt/rag/retrievers/milvus_retriever.py b/metagpt/rag/retrievers/milvus_retriever.py new file mode 100644 index 000000000..bcc66330b --- /dev/null +++ b/metagpt/rag/retrievers/milvus_retriever.py @@ -0,0 +1,17 @@ +"""Milvus retriever.""" + +from llama_index.core.retrievers import VectorIndexRetriever +from llama_index.core.schema import BaseNode + + +class MilvusRetriever(VectorIndexRetriever): + """Milvus retriever.""" + + def add_nodes(self, nodes: list[BaseNode], **kwargs) -> None: + """Support add nodes.""" + self._index.insert_nodes(nodes, **kwargs) + + def persist(self, persist_dir: str, **kwargs) -> None: + """Support persist. + + Milvus automatically saves, so there is no need to implement.""" diff --git a/metagpt/rag/schema.py b/metagpt/rag/schema.py index a8a10f90e..1e04a546f 100644 --- a/metagpt/rag/schema.py +++ b/metagpt/rag/schema.py @@ -62,6 +62,36 @@ class BM25RetrieverConfig(IndexRetrieverConfig): _no_embedding: bool = PrivateAttr(default=True) +class MilvusRetrieverConfig(IndexRetrieverConfig): + """Config for Milvus-based retrievers.""" + + uri: str = Field(default="./milvus_local.db", description="The directory to save data.") + collection_name: str = Field(default="metagpt", description="The name of the collection.") + token: str = Field(default=None, description="The token for Milvus") + metadata: Optional[CollectionMetadata] = Field( + default=None, description="Optional metadata to associate with the collection" + ) + dimensions: int = Field(default=0, description="Dimensionality of the vectors for Milvus index construction.") + + _embedding_type_to_dimensions: ClassVar[dict[EmbeddingType, int]] = { + EmbeddingType.GEMINI: 768, + EmbeddingType.OLLAMA: 4096, + } + + @model_validator(mode="after") + def check_dimensions(self): + if self.dimensions == 0: + self.dimensions = config.embedding.dimensions or self._embedding_type_to_dimensions.get( + config.embedding.api_type, 1536 + ) + if not config.embedding.dimensions and config.embedding.api_type not in self._embedding_type_to_dimensions: + logger.warning( + f"You didn't set dimensions in config when using {config.embedding.api_type}, default to 1536" + ) + + return self + + class ChromaRetrieverConfig(IndexRetrieverConfig): """Config for Chroma-based retrievers.""" @@ -170,6 +200,17 @@ class ChromaIndexConfig(VectorIndexConfig): ) +class MilvusIndexConfig(VectorIndexConfig): + """Config for milvus-based index.""" + + collection_name: str = Field(default="metagpt", description="The name of the collection.") + uri: str = Field(default="./milvus_local.db", description="The uri of the index.") + token: Optional[str] = Field(default=None, description="The token of the index.") + metadata: Optional[CollectionMetadata] = Field( + default=None, description="Optional metadata to associate with the collection" + ) + + class BM25IndexConfig(BaseIndexConfig): """Config for bm25-based index.""" diff --git a/requirements.txt b/requirements.txt index 8bf0ee399..5344ffb8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,14 +12,14 @@ typer==0.9.0 lancedb==0.4.0 loguru==0.6.0 meilisearch==0.21.0 -numpy>=1.24.3 -openai>=1.6.1 -openpyxl +numpy~=1.26.4 +openai~=1.39.0 +openpyxl~=3.1.5 beautifulsoup4==4.12.3 pandas==2.1.1 pydantic>=2.5.3 #pygame==2.1.3 -#pymilvus==2.2.8 +# pymilvus==2.4.6 # pytest==7.2.2 # test extras require python_docx==0.8.11 PyYAML==6.0.1 @@ -35,6 +35,9 @@ anthropic==0.18.1 typing-inspect==0.8.0 libcst==1.0.1 qdrant-client==1.7.0 +grpcio~=1.67.0 +grpcio-tools~=1.62.3 +grpcio-status~=1.62.3 # pytest-mock==3.11.1 # test extras require # open-interpreter==0.1.7; python_version>"3.9" # Conflict with openai 1.x ta==0.10.2 @@ -72,10 +75,10 @@ qianfan~=0.4.4 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 +volcengine-python-sdk[ark]~=1.0.94 # Solution for installation error in Windows: https://github.com/volcengine/volcengine-python-sdk/issues/5 # 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 -agentops +agentops \ No newline at end of file diff --git a/setup.py b/setup.py index 9fecfa766..a996a1eb7 100644 --- a/setup.py +++ b/setup.py @@ -43,6 +43,7 @@ extras_require = { "llama-index-postprocessor-cohere-rerank==0.1.4", "llama-index-postprocessor-colbert-rerank==0.1.1", "llama-index-postprocessor-flag-embedding-reranker==0.1.2", + # "llama-index-vector-stores-milvus==0.1.23", "docx2txt==0.8", ], } @@ -60,8 +61,6 @@ extras_require["test"] = [ "azure-cognitiveservices-speech~=1.31.0", "aioboto3~=12.4.0", "gradio==3.0.0", - "grpcio-status==1.48.2", - "grpcio-tools==1.48.2", "google-api-core==2.17.1", "protobuf==3.19.6", "pylint==3.0.3", diff --git a/tests/metagpt/document_store/test_milvus_store.py b/tests/metagpt/document_store/test_milvus_store.py new file mode 100644 index 000000000..93d4187f9 --- /dev/null +++ b/tests/metagpt/document_store/test_milvus_store.py @@ -0,0 +1,48 @@ +import random + +import pytest + +from metagpt.document_store.milvus_store import MilvusConnection, MilvusStore + +seed_value = 42 +random.seed(seed_value) + +vectors = [[random.random() for _ in range(8)] for _ in range(10)] +ids = [f"doc_{i}" for i in range(10)] +metadata = [{"color": "red", "rand_number": i % 10} for i in range(10)] + + +def assert_almost_equal(actual, expected): + delta = 1e-10 + if isinstance(expected, list): + assert len(actual) == len(expected) + for ac, exp in zip(actual, expected): + assert abs(ac - exp) <= delta, f"{ac} is not within {delta} of {exp}" + else: + assert abs(actual - expected) <= delta, f"{actual} is not within {delta} of {expected}" + + +@pytest.mark.skip() # Skip because the pymilvus dependency is not installed by default +def test_milvus_store(): + milvus_connection = MilvusConnection(uri="./milvus_local.db") + milvus_store = MilvusStore(milvus_connection) + + collection_name = "TestCollection" + milvus_store.create_collection(collection_name, dim=8) + + milvus_store.add(collection_name, ids, vectors, metadata) + + search_results = milvus_store.search(collection_name, query=[1.0] * 8) + assert len(search_results) > 0 + first_result = search_results[0] + assert first_result["id"] == "doc_0" + + search_results_with_filter = milvus_store.search(collection_name, query=[1.0] * 8, filter={"rand_number": 1}) + assert len(search_results_with_filter) > 0 + assert search_results_with_filter[0]["id"] == "doc_1" + + milvus_store.delete(collection_name, _ids=["doc_0"]) + deleted_results = milvus_store.search(collection_name, query=[1.0] * 8, limit=1) + assert deleted_results[0]["id"] != "doc_0" + + milvus_store.client.drop_collection(collection_name) diff --git a/tests/metagpt/provider/test_bedrock_api.py b/tests/metagpt/provider/test_bedrock_api.py index b9c9e0f93..28d1d7008 100644 --- a/tests/metagpt/provider/test_bedrock_api.py +++ b/tests/metagpt/provider/test_bedrock_api.py @@ -3,7 +3,7 @@ import json import pytest from metagpt.provider.bedrock.utils import ( - NOT_SUUPORT_STREAM_MODELS, + NOT_SUPPORT_STREAM_MODELS, SUPPORT_STREAM_MODELS, ) from metagpt.provider.bedrock_api import BedrockLLM @@ -14,7 +14,7 @@ from tests.metagpt.provider.req_resp_const import ( ) # all available model from bedrock -models = SUPPORT_STREAM_MODELS | NOT_SUUPORT_STREAM_MODELS +models = SUPPORT_STREAM_MODELS | NOT_SUPPORT_STREAM_MODELS messages = [{"role": "user", "content": "Hi!"}] usage = { "prompt_tokens": 1000000, diff --git a/tests/metagpt/rag/factories/test_index.py b/tests/metagpt/rag/factories/test_index.py index 9dc5bfb6b..e084eb6e7 100644 --- a/tests/metagpt/rag/factories/test_index.py +++ b/tests/metagpt/rag/factories/test_index.py @@ -8,6 +8,7 @@ from metagpt.rag.schema import ( ElasticsearchIndexConfig, ElasticsearchStoreConfig, FAISSIndexConfig, + MilvusIndexConfig, ) @@ -20,6 +21,10 @@ class TestRAGIndexFactory: def faiss_config(self): return FAISSIndexConfig(persist_path="") + @pytest.fixture + def milvus_config(self): + return MilvusIndexConfig(uri="", collection_name="") + @pytest.fixture def chroma_config(self): return ChromaIndexConfig(persist_path="", collection_name="") @@ -65,6 +70,16 @@ class TestRAGIndexFactory: ): self.index_factory.get_index(bm25_config, embed_model=mock_embedding) + def test_create_milvus_index(self, mocker, milvus_config, mock_from_vector_store, mock_embedding): + # Mock + mock_milvus_store = mocker.patch("metagpt.rag.factories.index.MilvusVectorStore") + + # Exec + self.index_factory.get_index(milvus_config, embed_model=mock_embedding) + + # Assert + mock_milvus_store.assert_called_once() + def test_create_chroma_index(self, mocker, chroma_config, mock_from_vector_store, mock_embedding): # Mock mock_chroma_db = mocker.patch("metagpt.rag.factories.index.chromadb.PersistentClient") diff --git a/tests/metagpt/rag/factories/test_retriever.py b/tests/metagpt/rag/factories/test_retriever.py index cd55a32db..b808de26e 100644 --- a/tests/metagpt/rag/factories/test_retriever.py +++ b/tests/metagpt/rag/factories/test_retriever.py @@ -5,6 +5,7 @@ from llama_index.core.embeddings import MockEmbedding from llama_index.core.schema import TextNode from llama_index.vector_stores.chroma import ChromaVectorStore from llama_index.vector_stores.elasticsearch import ElasticsearchStore +from llama_index.vector_stores.milvus import MilvusVectorStore from metagpt.rag.factories.retriever import RetrieverFactory from metagpt.rag.retrievers.bm25_retriever import DynamicBM25Retriever @@ -12,12 +13,14 @@ from metagpt.rag.retrievers.chroma_retriever import ChromaRetriever from metagpt.rag.retrievers.es_retriever import ElasticsearchRetriever from metagpt.rag.retrievers.faiss_retriever import FAISSRetriever from metagpt.rag.retrievers.hybrid_retriever import SimpleHybridRetriever +from metagpt.rag.retrievers.milvus_retriever import MilvusRetriever from metagpt.rag.schema import ( BM25RetrieverConfig, ChromaRetrieverConfig, ElasticsearchRetrieverConfig, ElasticsearchStoreConfig, FAISSRetrieverConfig, + MilvusRetrieverConfig, ) @@ -41,6 +44,10 @@ class TestRetrieverFactory: def mock_chroma_vector_store(self, mocker): return mocker.MagicMock(spec=ChromaVectorStore) + @pytest.fixture + def mock_milvus_vector_store(self, mocker): + return mocker.MagicMock(spec=MilvusVectorStore) + @pytest.fixture def mock_es_vector_store(self, mocker): return mocker.MagicMock(spec=ElasticsearchStore) @@ -91,6 +98,14 @@ class TestRetrieverFactory: assert isinstance(retriever, ChromaRetriever) + def test_get_retriever_with_milvus_config(self, mocker, mock_milvus_vector_store, mock_embedding): + mock_config = MilvusRetrieverConfig(uri="/path/to/milvus.db", collection_name="test_collection") + mocker.patch("metagpt.rag.factories.retriever.MilvusVectorStore", return_value=mock_milvus_vector_store) + + retriever = self.retriever_factory.get_retriever(configs=[mock_config], nodes=[], embed_model=mock_embedding) + + assert isinstance(retriever, MilvusRetriever) + def test_get_retriever_with_es_config(self, mocker, mock_es_vector_store, mock_embedding): mock_config = ElasticsearchRetrieverConfig(store_config=ElasticsearchStoreConfig()) mocker.patch("metagpt.rag.factories.retriever.ElasticsearchStore", return_value=mock_es_vector_store)