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
-
+
@@ -23,11 +23,11 @@ # MetaGPT: The Multi-Agent Framework
-
+
## 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: 多智能体框架
-
+
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: マルチエージェントフレームワーク
-
+
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)