aisuite4cn 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,18 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Huwei
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
6
+ associated documentation files (the "Software"), to deal in the Software without restriction, including
7
+ without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the
9
+ following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be included in all copies or substantial
12
+ portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
15
+ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
16
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
17
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
18
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,40 @@
1
+ Metadata-Version: 2.1
2
+ Name: aisuite4cn
3
+ Version: 0.1.0
4
+ Summary:
5
+ Author: 胡伟
6
+ Author-email: 3314672@qq.com
7
+ Requires-Python: >=3.10,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Programming Language :: Python :: 3.13
13
+ Provides-Extra: moonshot
14
+ Provides-Extra: qwen
15
+ Provides-Extra: volcengine
16
+ Requires-Dist: httpx (>=0.27.0,<0.28.0)
17
+ Requires-Dist: openai (>=1.35.8,<2.0.0) ; extra == "moonshot" or extra == "volcengine" or extra == "qwen"
18
+ Requires-Dist: pydantic (>=2.10.3,<3.0.0)
19
+ Description-Content-Type: text/markdown
20
+
21
+ # aisuite4cn
22
+
23
+ 简单、统一的接口,可连接多个生成式人工智能提供商。
24
+
25
+ `aisuite4cn` 针对于中国的各类大模型厂商提供通用的支持。学习了`aisuite`方案,并开发了该库。
26
+
27
+ `aisuite4cn` 使得开发者能够通过标准化的接口轻松使用多个大型语言模型(LLM)。使用类似于OpenAI的接口,`aisuite4cn` 使得与最受欢迎的LLM互动并比较结果变得简单。它是Python客户端库的轻量级包装器,允许创造者在不改变代码的情况下无缝切换并测试来自不同LLM提供商的响应。我们将在不久的将来扩展它以覆盖更多的用例。
28
+
29
+ 当前支持的提供商包括:
30
+ moonshot(月之暗面)、Doubao(火山引擎)、Qwen(阿里云千问大模型)。
31
+
32
+
33
+ ## Integrated Open source project
34
+ Special thanks to all contributors
35
+
36
+ ### aisuite
37
+ https://github.com/andrewyng/aisuite
38
+
39
+ ### openai-python
40
+ https://github.com/openai/openai-python
@@ -0,0 +1,20 @@
1
+ # aisuite4cn
2
+
3
+ 简单、统一的接口,可连接多个生成式人工智能提供商。
4
+
5
+ `aisuite4cn` 针对于中国的各类大模型厂商提供通用的支持。学习了`aisuite`方案,并开发了该库。
6
+
7
+ `aisuite4cn` 使得开发者能够通过标准化的接口轻松使用多个大型语言模型(LLM)。使用类似于OpenAI的接口,`aisuite4cn` 使得与最受欢迎的LLM互动并比较结果变得简单。它是Python客户端库的轻量级包装器,允许创造者在不改变代码的情况下无缝切换并测试来自不同LLM提供商的响应。我们将在不久的将来扩展它以覆盖更多的用例。
8
+
9
+ 当前支持的提供商包括:
10
+ moonshot(月之暗面)、Doubao(火山引擎)、Qwen(阿里云千问大模型)。
11
+
12
+
13
+ ## Integrated Open source project
14
+ Special thanks to all contributors
15
+
16
+ ### aisuite
17
+ https://github.com/andrewyng/aisuite
18
+
19
+ ### openai-python
20
+ https://github.com/openai/openai-python
@@ -0,0 +1 @@
1
+ from .client import Client
@@ -0,0 +1,117 @@
1
+ from .provider import ProviderFactory
2
+
3
+
4
+ class Client:
5
+ def __init__(self, provider_configs: dict = {}):
6
+ """
7
+ Initialize the client with provider configurations.
8
+ Use the ProviderFactory to create provider instances.
9
+
10
+ Args:
11
+ provider_configs (dict): A dictionary containing provider configurations.
12
+ Each key should be a provider string (e.g., "google" or "aws-bedrock"),
13
+ and the value should be a dictionary of configuration options for that provider.
14
+ For example:
15
+ {
16
+ "openai": {"api_key": "your_openai_api_key"},
17
+ "aws-bedrock": {
18
+ "aws_access_key": "your_aws_access_key",
19
+ "aws_secret_key": "your_aws_secret_key",
20
+ "aws_region": "us-west-2"
21
+ }
22
+ }
23
+ """
24
+ self.providers = {}
25
+ self.provider_configs = provider_configs
26
+ self._chat = None
27
+ self._initialize_providers()
28
+
29
+ def _initialize_providers(self):
30
+ """Helper method to initialize or update providers."""
31
+ for provider_key, config in self.provider_configs.items():
32
+ provider_key = self._validate_provider_key(provider_key)
33
+ self.providers[provider_key] = ProviderFactory.create_provider(
34
+ provider_key, config
35
+ )
36
+
37
+ def _validate_provider_key(self, provider_key):
38
+ """
39
+ Validate if the provider key corresponds to a supported provider.
40
+ """
41
+ supported_providers = ProviderFactory.get_supported_providers()
42
+
43
+ if provider_key not in supported_providers:
44
+ raise ValueError(
45
+ f"Invalid provider key '{provider_key}'. Supported providers: {supported_providers}. "
46
+ "Make sure the model string is formatted correctly as 'provider:model'."
47
+ )
48
+
49
+ return provider_key
50
+
51
+ def configure(self, provider_configs: dict = None):
52
+ """
53
+ Configure the client with provider configurations.
54
+ """
55
+ if provider_configs is None:
56
+ return
57
+
58
+ self.provider_configs.update(provider_configs)
59
+ self._initialize_providers() # NOTE: This will override existing provider instances.
60
+
61
+ @property
62
+ def chat(self):
63
+ """Return the chat API interface."""
64
+ if not self._chat:
65
+ self._chat = Chat(self)
66
+ return self._chat
67
+
68
+
69
+ class Chat:
70
+ def __init__(self, client: "Client"):
71
+ self.client = client
72
+ self._completions = Completions(self.client)
73
+
74
+ @property
75
+ def completions(self):
76
+ """Return the completions interface."""
77
+ return self._completions
78
+
79
+
80
+ class Completions:
81
+ def __init__(self, client: "Client"):
82
+ self.client = client
83
+
84
+ def create(self, model: str, messages: list, **kwargs):
85
+ """
86
+ Create chat completion based on the model, messages, and any extra arguments.
87
+ """
88
+ # Check that correct format is used
89
+ if ":" not in model:
90
+ raise ValueError(
91
+ f"Invalid model format. Expected 'provider:model', got '{model}'"
92
+ )
93
+
94
+ # Extract the provider key from the model identifier, e.g., "google:gemini-xx"
95
+ provider_key, model_name = model.split(":", 1)
96
+
97
+ # Validate if the provider is supported
98
+ supported_providers = ProviderFactory.get_supported_providers()
99
+ if provider_key not in supported_providers:
100
+ raise ValueError(
101
+ f"Invalid provider key '{provider_key}'. Supported providers: {supported_providers}. "
102
+ "Make sure the model string is formatted correctly as 'provider:model'."
103
+ )
104
+
105
+ # Initialize provider if not already initialized
106
+ if provider_key not in self.client.providers:
107
+ config = self.client.provider_configs.get(provider_key, {})
108
+ self.client.providers[provider_key] = ProviderFactory.create_provider(
109
+ provider_key, config
110
+ )
111
+
112
+ provider = self.client.providers.get(provider_key)
113
+ if not provider:
114
+ raise ValueError(f"Could not load provider for '{provider_key}'.")
115
+
116
+ # Delegate the chat completion to the correct provider's implementation
117
+ return provider.chat_completions_create(model_name, messages, **kwargs)
@@ -0,0 +1,2 @@
1
+ from .provider_interface import ProviderInterface
2
+ from .chat_completion_response import ChatCompletionResponse
@@ -0,0 +1,8 @@
1
+ from .choice import Choice
2
+
3
+
4
+ class ChatCompletionResponse:
5
+ """Used to conform to the response model of OpenAI"""
6
+
7
+ def __init__(self):
8
+ self.choices = [Choice()] # Adjust the range as needed for more choices
@@ -0,0 +1,6 @@
1
+ from .message import Message
2
+
3
+
4
+ class Choice:
5
+ def __init__(self):
6
+ self.message = Message()
@@ -0,0 +1,6 @@
1
+ """Interface to hold contents of api responses when they do not confirm to the OpenAI style response"""
2
+
3
+
4
+ class Message:
5
+ def __init__(self):
6
+ self.content = None
@@ -0,0 +1,25 @@
1
+ """The shared interface for model providers."""
2
+
3
+
4
+ class ProviderInterface:
5
+ """Defines the expected behavior for provider-specific interfaces."""
6
+
7
+ def chat_completion_create(self, messages=None, model=None, temperature=0) -> None:
8
+ """Create a chat completion using the specified messages, model, and temperature.
9
+
10
+ This method must be implemented by subclasses to perform completions.
11
+
12
+ Args:
13
+ ----
14
+ messages (list): The chat history.
15
+ model (str): The identifier of the model to be used in the completion.
16
+ temperature (float): The temperature to use in the completion.
17
+
18
+ Raises:
19
+ ------
20
+ NotImplementedError: If this method has not been implemented by a subclass.
21
+
22
+ """
23
+ raise NotImplementedError(
24
+ "Provider Interface has not implemented chat_completion_create()"
25
+ )
@@ -0,0 +1,53 @@
1
+ from abc import ABC, abstractmethod
2
+ from pathlib import Path
3
+ import importlib
4
+ import os
5
+ import functools
6
+
7
+
8
+ class LLMError(Exception):
9
+ """Custom exception for LLM errors."""
10
+
11
+ def __init__(self, message):
12
+ super().__init__(message)
13
+
14
+
15
+ class Provider(ABC):
16
+ @abstractmethod
17
+ def chat_completions_create(self, model, messages):
18
+ """Abstract method for chat completion calls, to be implemented by each provider."""
19
+ pass
20
+
21
+
22
+ class ProviderFactory:
23
+ """Factory to dynamically load provider instances based on naming conventions."""
24
+
25
+ PROVIDERS_DIR = Path(__file__).parent / "providers"
26
+
27
+ @classmethod
28
+ def create_provider(cls, provider_key, config):
29
+ """Dynamically load and create an instance of a provider based on the naming convention."""
30
+ # Convert provider_key to the expected module and class names
31
+ provider_class_name = f"{provider_key.capitalize()}Provider"
32
+ provider_module_name = f"{provider_key}_provider"
33
+
34
+ module_path = f"aisuite.providers.{provider_module_name}"
35
+
36
+ # Lazily load the module
37
+ try:
38
+ module = importlib.import_module(module_path)
39
+ except ImportError as e:
40
+ raise ImportError(
41
+ f"Could not import module {module_path}: {str(e)}. Please ensure the provider is supported by doing ProviderFactory.get_supported_providers()"
42
+ )
43
+
44
+ # Instantiate the provider class
45
+ provider_class = getattr(module, provider_class_name)
46
+ return provider_class(**config)
47
+
48
+ @classmethod
49
+ @functools.cache
50
+ def get_supported_providers(cls):
51
+ """List all supported provider names based on files present in the providers directory."""
52
+ provider_files = Path(cls.PROVIDERS_DIR).glob("*_provider.py")
53
+ return {file.stem.replace("_provider", "") for file in provider_files}
File without changes
@@ -0,0 +1,37 @@
1
+ import openai
2
+ import os
3
+ from aisuite4cn.provider import Provider, LLMError
4
+
5
+
6
+ class MoonshotProvider(Provider):
7
+ """
8
+ 月之暗面提供者
9
+ """
10
+ def __init__(self, **config):
11
+ """
12
+ Initialize the Moonshot provider with the given configuration.
13
+ Pass the entire configuration dictionary to the Moonshot client constructor.
14
+ """
15
+ # Ensure API key is provided either in config or via environment variable
16
+ self.api_key = config.get("api_key") or os.getenv("MOONSHOT_API_KEY")
17
+ if not self.api_key:
18
+ raise ValueError(
19
+ "Moonshot API key is missing. Please provide it in the config or set the MOONSHOT_API_KEY environment variable."
20
+ )
21
+ kvargs = dict(config)
22
+ kvargs.pop("api_key")
23
+ # Pass the entire config to the Moonshot client constructor
24
+ self.client = openai.OpenAI(
25
+ api_key = self.api_key,
26
+ base_url = "https://api.moonshot.cn/v1",
27
+ **kvargs)
28
+
29
+ def chat_completions_create(self, model, messages, **kwargs):
30
+ # Any exception raised by Moonshot will be returned to the caller.
31
+ # Maybe we should catch them and raise a custom LLMError.
32
+ return self.client.chat.completions.create(
33
+ model=model,
34
+ messages=messages,
35
+ **kwargs # Pass any additional arguments to the Moonshot API
36
+ )
37
+
@@ -0,0 +1,36 @@
1
+ import openai
2
+ import os
3
+ from aisuite4cn.provider import Provider, LLMError
4
+
5
+
6
+ class QwenProvider(Provider):
7
+ """
8
+ 阿里云大模型服务平台针对于千问大模型的提供者,使用了千问大模型兼容OpenAI的接口
9
+ """
10
+ def __init__(self, **config):
11
+ """
12
+ Initialize the Qwen shot provider with the given configuration.
13
+ Pass the entire configuration dictionary to the Qwen client constructor.
14
+ """
15
+ # Ensure API key is provided either in config or via environment variable
16
+ self.api_key = config.get("api_key") or os.getenv("DASHSCOPE_API_KEY")
17
+ if not self.api_key:
18
+ raise ValueError(
19
+ "Dashscope API key is missing. Please provide it in the config or set the DASHSCOPE_API_KEY environment variable."
20
+ )
21
+ kvargs = dict(config)
22
+ kvargs.pop("api_key")
23
+ # Pass the entire config to the Qwen client constructor
24
+ self.client = openai.OpenAI(
25
+ api_key=self.api_key,
26
+ base_url='https://dashscope.aliyuncs.com/compatible-mode/v1',
27
+ **kvargs)
28
+
29
+ def chat_completions_create(self, model, messages, **kwargs):
30
+ # Any exception raised by Qwen will be returned to the caller.
31
+ # Maybe we should catch them and raise a custom LLMError.
32
+ return self.client.chat.completions.create(
33
+ model=model,
34
+ messages=messages,
35
+ **kwargs # Pass any additional arguments to the Qwen API
36
+ )
@@ -0,0 +1,46 @@
1
+ from argparse import ArgumentError
2
+
3
+ import openai
4
+ import os
5
+ from aisuite4cn.provider import Provider, LLMError
6
+
7
+
8
+ class VolcengineProvider(Provider):
9
+ """
10
+ 火山引擎方舟大模型服务平台提供者
11
+ """
12
+ def __init__(self, **config):
13
+ """
14
+ Initialize the Volcengine provider with the given configuration.
15
+ Pass the entire configuration dictionary to the Volcengine client constructor.
16
+ """
17
+ # Ensure API key is provided either in config or via environment variable
18
+ self.api_key = config.get("api_key") or os.getenv("ARK_API_KEY")
19
+
20
+ if not self.api_key:
21
+ raise ValueError(
22
+ "Ark API key is missing. Please provide it in the config or set the OPENAI_API_KEY environment variable."
23
+ )
24
+ self.config = config
25
+
26
+ kvargs = dict(config)
27
+ kvargs.pop("api_key")
28
+ # Pass the entire config to the Ark client constructor
29
+
30
+ self.client = openai.OpenAI(
31
+ api_key=self.api_key,
32
+ base_url="https://ark.cn-beijing.volces.com/api/v3",
33
+ **kvargs)
34
+
35
+ def chat_completions_create(self, model, messages, **kwargs):
36
+
37
+ # Any exception raised by Ark will be returned to the caller.
38
+ # Maybe we should catch them and raise a custom LLMError.
39
+
40
+ if not model.startswith("ep-"):
41
+ raise ValueError("The model name must be the endpoint ID of the model.")
42
+ return self.client.chat.completions.create(
43
+ model=model,
44
+ messages=messages,
45
+ **kwargs # Pass any additional arguments to the Ark API
46
+ )
@@ -0,0 +1,33 @@
1
+ [tool.poetry]
2
+ name = "aisuite4cn"
3
+ version = "0.1.0"
4
+ description = ""
5
+ authors = ["胡伟 <3314672@qq.com>"]
6
+ readme = "README.md"
7
+
8
+ [tool.poetry.dependencies]
9
+ python = "^3.10"
10
+ pydantic = "^2.10.3"
11
+ openai = { version = "^1.35.8", optional = true }
12
+
13
+ # Optional dependencies for different providers
14
+ httpx = "~0.27.0"
15
+
16
+ [tool.poetry.extras]
17
+ moonshot = ["openai"]
18
+ volcengine = ["openai"]
19
+ qwen = ["openai"]
20
+
21
+ [tool.poetry.group.dev.dependencies]
22
+ openai = "^1.35.8"
23
+
24
+ [tool.poetry.group.test]
25
+ optional = true
26
+
27
+ [tool.poetry.group.test.dependencies]
28
+ pytest = "^8.2.2"
29
+ pytest-cov = "^6.0.0"
30
+
31
+ [build-system]
32
+ requires = ["poetry-core"]
33
+ build-backend = "poetry.core.masonry.api"