unionllm 0.1.0__py3-none-any.whl

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.
Files changed (57) hide show
  1. cnlitellm/__init__.py +0 -0
  2. cnlitellm/api.py +40 -0
  3. cnlitellm/exceptions.py +11 -0
  4. cnlitellm/models.py +28 -0
  5. cnlitellm/providers/__init__.py +0 -0
  6. cnlitellm/providers/baichuan.py +128 -0
  7. cnlitellm/providers/base_provider.py +44 -0
  8. cnlitellm/providers/coze.py +230 -0
  9. cnlitellm/providers/dify.py +192 -0
  10. cnlitellm/providers/example_provider.py +19 -0
  11. cnlitellm/providers/fastgpt.py +171 -0
  12. cnlitellm/providers/minimax.py +122 -0
  13. cnlitellm/providers/moonshot.py +69 -0
  14. cnlitellm/providers/qwen.py +168 -0
  15. cnlitellm/providers/tiangong.py +145 -0
  16. cnlitellm/providers/wenxin.py +161 -0
  17. cnlitellm/providers/xunfei.py +245 -0
  18. cnlitellm/providers/zhipu.py +63 -0
  19. cnlitellm/utils.py +432 -0
  20. tests/__init__.py +0 -0
  21. tests/test_api.py +4 -0
  22. tests/test_baichuan.py +56 -0
  23. tests/test_coze.py +33 -0
  24. tests/test_fastgpt.py +44 -0
  25. tests/test_litellm.py +24 -0
  26. tests/test_minimax.py +51 -0
  27. tests/test_moonshot.py +78 -0
  28. tests/test_moonshot2.py +43 -0
  29. tests/test_qwen.py +53 -0
  30. tests/test_tiangong.py +54 -0
  31. tests/test_wenxin.py +54 -0
  32. tests/test_zhipu.py +47 -0
  33. tests/test_zhipu2.py +42 -0
  34. unionllm/__init__.py +1 -0
  35. unionllm/exceptions.py +11 -0
  36. unionllm/main.py +67 -0
  37. unionllm/models.py +28 -0
  38. unionllm/providers/__init__.py +0 -0
  39. unionllm/providers/baichuan.py +128 -0
  40. unionllm/providers/base_provider.py +51 -0
  41. unionllm/providers/coze.py +230 -0
  42. unionllm/providers/dify.py +192 -0
  43. unionllm/providers/fastgpt.py +171 -0
  44. unionllm/providers/litellm.py +63 -0
  45. unionllm/providers/minimax.py +122 -0
  46. unionllm/providers/moonshot.py +69 -0
  47. unionllm/providers/qwen.py +168 -0
  48. unionllm/providers/tiangong.py +145 -0
  49. unionllm/providers/wenxin.py +161 -0
  50. unionllm/providers/xunfei.py +245 -0
  51. unionllm/providers/zhipu.py +63 -0
  52. unionllm/utils.py +432 -0
  53. unionllm-0.1.0.dist-info/LICENSE +3 -0
  54. unionllm-0.1.0.dist-info/METADATA +17 -0
  55. unionllm-0.1.0.dist-info/RECORD +57 -0
  56. unionllm-0.1.0.dist-info/WHEEL +5 -0
  57. unionllm-0.1.0.dist-info/top_level.txt +2 -0
cnlitellm/__init__.py ADDED
File without changes
cnlitellm/api.py ADDED
@@ -0,0 +1,40 @@
1
+ import logging
2
+ from typing import Any, List
3
+ from .providers import base_provider
4
+ from .providers import zhipu, moonshot, minimax, qwen, tiangong, baichuan, wenxin, xunfei, dify, fastgpt, coze
5
+ from .exceptions import ProviderError
6
+
7
+ logger = logging.getLogger(__name__)
8
+
9
+ class CNLiteLLM:
10
+ def __init__(self, provider: str, **kwargs):
11
+ self.provider = provider.lower()
12
+ if self.provider == "zhipuai":
13
+ self.provider_instance = zhipu.ZhipuAIProvider(**kwargs)
14
+ elif self.provider == "moonshot":
15
+ self.provider_instance = moonshot.MoonshotAIProvider(**kwargs)
16
+ elif self.provider == "minimax":
17
+ self.provider_instance = minimax.MinimaxAIProvider(**kwargs)
18
+ elif self.provider == "qwen":
19
+ self.provider_instance = qwen.QwenAIProvider(**kwargs)
20
+ elif self.provider == "tiangong":
21
+ self.provider_instance = tiangong.TianGongAIProvider(**kwargs)
22
+ elif self.provider == "baichuan":
23
+ self.provider_instance = baichuan.BaiChuanAIProvider(**kwargs)
24
+ elif self.provider == "wenxin":
25
+ self.provider_instance = wenxin.WenXinAIProvider(**kwargs)
26
+ elif self.provider == "xunfei":
27
+ self.provider_instance = xunfei.XunfeiAIProvider(**kwargs)
28
+ elif self.provider == "dify":
29
+ self.provider_instance = dify.DifyAIProvider(**kwargs)
30
+ elif self.provider == "fastgpt":
31
+ self.provider_instance = fastgpt.FastGPTProvider(**kwargs)
32
+ elif self.provider == "coze":
33
+ self.provider_instance = coze.CozeAIProvider(**kwargs)
34
+ else:
35
+ raise ProviderError(f"Provider '{self.provider}' is not supported.")
36
+
37
+ def completion(self, model: str, messages: List[str], **kwargs) -> Any:
38
+ if not self.provider_instance:
39
+ raise ProviderError(f"Provider '{self.provider}' is not initialized.")
40
+ return self.provider_instance.completion(model, messages, **kwargs)
@@ -0,0 +1,11 @@
1
+ class CNLiteLLMError(Exception):
2
+ """Base exception class for CNLiteLLM."""
3
+ pass
4
+
5
+ class ProviderError(CNLiteLLMError):
6
+ """Exception raised when there is an issue with the model provider."""
7
+ pass
8
+
9
+ class APICallError(CNLiteLLMError):
10
+ """Exception raised when an API call fails."""
11
+ pass
cnlitellm/models.py ADDED
@@ -0,0 +1,28 @@
1
+ from typing import List, Optional
2
+
3
+ class ResponseModel:
4
+ def __init__(self, prompt_tokens: List[str], completion_tokens: List[str], total_tokens: int, total_attempts: int, raw_response: Optional[dict] = None, usage: Optional[dict] = None):
5
+ self.prompt_tokens = prompt_tokens
6
+ self.completion_tokens = completion_tokens
7
+ self.total_tokens = total_tokens
8
+ self.total_attempts = total_attempts
9
+ self.raw_response = raw_response
10
+ self.usage = usage
11
+
12
+ def to_dict(self) -> dict:
13
+ return {
14
+ "prompt_tokens": self.prompt_tokens,
15
+ "completion_tokens": self.completion_tokens,
16
+ "total_tokens": self.total_tokens,
17
+ "total_attempts": self.total_attempts,
18
+ "raw_response": self.raw_response,
19
+ "usage": self.usage
20
+ }
21
+
22
+ def get_completions(self) -> list[str]:
23
+ completions = []
24
+ if self.raw_response and "choices" in self.raw_response:
25
+ for choice in self.raw_response["choices"]:
26
+ if "message" in choice and "content" in choice["message"]:
27
+ completions.append(choice["message"]["content"])
28
+ return completions
File without changes
@@ -0,0 +1,128 @@
1
+ import json
2
+ from .base_provider import BaseProvider
3
+ from cnlitellm.utils import ModelResponse, Message, Choices, Usage, Delta, StreamingChoices
4
+ import requests
5
+ import logging
6
+
7
+ class BaiChuanOpenAIError(Exception):
8
+ def __init__(
9
+ self,
10
+ status_code,
11
+ message,
12
+ ):
13
+ self.status_code = status_code
14
+ self.message = message
15
+ super().__init__(self.message)
16
+
17
+ class BaiChuanAIProvider(BaseProvider):
18
+ def __init__(self, **model_kwargs):
19
+ self.api_key = model_kwargs.get("api_key")
20
+ self.endpoint_url = "https://api.baichuan-ai.com/v1/chat/completions"
21
+
22
+ def pre_processing(self, **kwargs):
23
+ supported_params = [
24
+ "model", "messages", "max_tokens", "temperature", "top_p", "n",
25
+ "logprobs", "stream", "stop", "presence_penalty", "frequency_penalty",
26
+ "best_of", "logit_bias"
27
+ ]
28
+ for key in list(kwargs.keys()):
29
+ if key not in supported_params:
30
+ kwargs.pop(key)
31
+ return kwargs
32
+
33
+ def post_stream_processing_wrapper(self, model, messages, **new_kwargs):
34
+ payload = json.dumps({"model": model, "messages": messages, **new_kwargs})
35
+ headers = {
36
+ "Authorization": f"Bearer {self.api_key}",
37
+ "Content-Type": "application/json",
38
+ }
39
+ response = requests.post(self.endpoint_url, headers=headers, data=payload)
40
+ for line in response.iter_lines():
41
+ if line:
42
+ new_line = line.decode("utf-8").replace("data: ", "")
43
+ if new_line == "[DONE]":
44
+ break
45
+ data = json.loads(new_line)
46
+ chunk_choices = []
47
+ for choice in data["choices"]:
48
+ chunk_delta = Delta()
49
+ delta = choice.get("delta")
50
+ if delta:
51
+ if "role" in choice['delta']:
52
+ chunk_delta.role = choice['delta']["role"]
53
+ if "content" in choice['delta']:
54
+ chunk_delta.content = choice['delta']["content"]
55
+ chunk_choices.append(StreamingChoices(index=choice['index'], delta=chunk_delta))
56
+
57
+ if "usage" in data:
58
+ chunk_usage = Usage()
59
+ if "prompt_tokens" in data["usage"]:
60
+ chunk_usage.prompt_tokens = data["usage"]["prompt_tokens"]
61
+ if "completion_tokens" in data["usage"]:
62
+ chunk_usage.completion_tokens = data["usage"]["completion_tokens"]
63
+ if "total_tokens" in data["usage"]:
64
+ chunk_usage.total_tokens = data["usage"]["total_tokens"]
65
+
66
+ chunk_response = ModelResponse(
67
+ id=data["id"],
68
+ choices=chunk_choices,
69
+ created=data["created"],
70
+ model=model,
71
+ usage=chunk_usage if "usage" in data else None,
72
+ stream=True
73
+ )
74
+ yield chunk_response
75
+
76
+ def create_model_response_wrapper(self, result, model):
77
+ response_dict = result.json()
78
+ choices = []
79
+ for choice in response_dict["choices"]:
80
+ message = Message(
81
+ content=choice["message"]["content"], role=choice["message"]["role"]
82
+ )
83
+ choices.append(
84
+ Choices(
85
+ message=message,
86
+ index=choice["index"],
87
+ finish_reason=choice["finish_reason"],
88
+ )
89
+ )
90
+ usage = Usage(
91
+ prompt_tokens=response_dict["usage"]["prompt_tokens"],
92
+ completion_tokens=response_dict["usage"]["completion_tokens"],
93
+ total_tokens=response_dict["usage"]["total_tokens"],
94
+ )
95
+ response = ModelResponse(
96
+ id=response_dict["id"],
97
+ choices=choices,
98
+ created=response_dict["created"],
99
+ model=model,
100
+ usage=usage,
101
+ )
102
+ return response
103
+
104
+
105
+ def completion(self, model: str, messages: list, **kwargs):
106
+ try:
107
+ if model is None or messages is None:
108
+ raise BaiChuanOpenAIError(
109
+ status_code=422, message=f"Missing model or messages"
110
+ )
111
+ new_kwargs = self.pre_processing(**kwargs)
112
+ stream = kwargs.get("stream", False)
113
+
114
+ if stream:
115
+ return self.post_stream_processing_wrapper(model, messages, **new_kwargs)
116
+ else:
117
+ payload = json.dumps({"model": model, "messages": messages, **new_kwargs})
118
+ headers = {
119
+ "Authorization": f"Bearer {self.api_key}",
120
+ "Content-Type": "application/json",
121
+ }
122
+ result = requests.post(self.endpoint_url, headers=headers, data=payload)
123
+ return self.create_model_response_wrapper(result, model=model)
124
+ except Exception as e:
125
+ if hasattr(e, "status_code"):
126
+ raise BaiChuanOpenAIError(status_code=e.status_code, message=str(e))
127
+ else:
128
+ raise BaiChuanOpenAIError(status_code=500, message=str(e))
@@ -0,0 +1,44 @@
1
+ from abc import ABC, abstractmethod
2
+ from ..models import ResponseModel
3
+ from typing import TYPE_CHECKING, Any, Dict, Optional, Sequence, List
4
+ from cnlitellm.utils import Message, Choices, Usage, ModelResponse
5
+
6
+ if TYPE_CHECKING:
7
+ from dataclasses import dataclass
8
+ else:
9
+ from pydantic.dataclasses import dataclass
10
+
11
+ @dataclass
12
+ class BaseProvider(ABC):
13
+ cls: str
14
+ args: Optional[Dict[str, Any]] = None
15
+ key: Optional[str] = None
16
+ group: Optional[str] = None
17
+
18
+ @abstractmethod
19
+ def completion(self, model: str, messages: list) -> ResponseModel:
20
+ pass
21
+
22
+ def create_model_response(self, openai_response: Any, model: str) -> ResponseModel:
23
+ print("openai_response: ", openai_response)
24
+ choices = []
25
+
26
+ for choice in openai_response.choices:
27
+ print("choice.message.content: ", choice.message.content)
28
+ message = Message(content=choice.message.content, role=choice.message.role)
29
+ print("message: ", message)
30
+ choices.append(Choices(message=message, index=choice.index, finish_reason=choice.finish_reason))
31
+
32
+ usage = Usage(
33
+ prompt_tokens=openai_response.usage.prompt_tokens,
34
+ completion_tokens=openai_response.usage.completion_tokens,
35
+ total_tokens=openai_response.usage.total_tokens
36
+ )
37
+ response = ModelResponse(
38
+ id=openai_response.id,
39
+ choices=choices,
40
+ created=openai_response.created,
41
+ model=model,
42
+ usage=usage
43
+ )
44
+ pass
@@ -0,0 +1,230 @@
1
+ from .base_provider import BaseProvider
2
+ from cnlitellm.utils import ResponseModelInterface
3
+ from cnlitellm.utils import ModelResponse, Message, Choices, Usage, Context, generate_unique_uid, Delta, StreamingChoices
4
+ from openai import OpenAI
5
+ import logging, json, time, requests
6
+
7
+
8
+ class CozeAIError(Exception):
9
+ def __init__(
10
+ self,
11
+ status_code,
12
+ message,
13
+ ):
14
+ self.status_code = status_code
15
+ self.message = message
16
+ super().__init__(self.message)
17
+
18
+
19
+ class CozeAIProvider(BaseProvider):
20
+ def __init__(self, **model_kwargs):
21
+ self.api_key = model_kwargs.get("api_key")
22
+ self.base_url = "https://api.coze.com/open_api/v2"
23
+ self.endpoint_url = self.base_url + "/chat"
24
+
25
+ def pre_processing(self, **kwargs):
26
+ # 处理参数兼容性问题,不支持的参数全部舍弃
27
+ supported_params = [
28
+ "model", "messages", "max_tokens", "temperature", "top_p", "n",
29
+ "logprobs", "stream", "stop", "presence_penalty", "frequency_penalty",
30
+ "best_of", "logit_bias"
31
+ ]
32
+ for key in list(kwargs.keys()):
33
+ if key not in supported_params:
34
+ kwargs.pop(key)
35
+ return kwargs
36
+
37
+ def to_formatted_prompt(self, messages):
38
+ message = messages[-1]
39
+ # if last message role is not user, return error
40
+ if message["role"] != "user":
41
+ raise CozeAIError(
42
+ status_code=422, message=f"Last message role should be user"
43
+ )
44
+ query = message["content"]
45
+ return messages, query
46
+
47
+ def post_stream_processing_wrapper(self, model, messages, **new_kwargs):
48
+ # 预处理消息内容并提取用户问题
49
+ messages, query = self.to_formatted_prompt(messages)
50
+
51
+ # 接收user_id作为用户唯一标识传入参数
52
+ if 'user_id' not in new_kwargs:
53
+ user_id = generate_unique_uid()
54
+ else:
55
+ user_id = new_kwargs['user_id']
56
+ payload = json.dumps({"query": query,"user": user_id,"bot_id": model,"chat_history":[], "stream": True})
57
+ headers = {
58
+ "Authorization": f"Bearer {self.api_key}",
59
+ "Content-Type": "application/json",
60
+ }
61
+ response = requests.post(self.endpoint_url, headers=headers, data=payload)
62
+
63
+ for line in response.iter_lines():
64
+ if line:
65
+ if line.startswith(b"data:"):
66
+ new_line = line.decode("utf-8").replace("data:", "")
67
+ if new_line == "[DONE]":
68
+ continue
69
+
70
+ data = json.loads(new_line)
71
+ print("data is:",data)
72
+ chunk_line = {}
73
+ chunk_context = []
74
+ chunk_choices = []
75
+ index = 0
76
+ if "message" in data:
77
+ message = data["message"]
78
+ if message['role'] == 'assistant' and message['type'] == 'knowledge':
79
+ # 解析知识型内容
80
+ content = message['content']
81
+ conversation_id = data.get('conversation_id', 0)
82
+ # 假设knowledge类型内容是由"---\nrecall slice X:\n"分隔的
83
+ slices = content.split('---\n')
84
+ index = 0
85
+ for slice in slices:
86
+ if slice.strip().startswith('recall slice'):
87
+ # 去掉前缀找到JSON部分
88
+ json_part = slice.strip().split('\n', 1)[-1].strip()
89
+ # 如果不是以\"}结尾,则强制添加
90
+ if not json_part.endswith('\"}'):
91
+ json_part += '\"}'
92
+ try:
93
+ # 尝试解析JSON数据
94
+ recall_data = json.loads(json_part)
95
+ chunk_context.append({
96
+ "id": index,
97
+ "content": str(recall_data)
98
+ })
99
+ index += 1
100
+ except json.JSONDecodeError:
101
+ raise CozeAIError(status_code=500, message=f"Error decoding JSON from slice: {json_part}")
102
+
103
+ elif message['role'] == 'assistant' and message['type'] == 'answer':
104
+ chunk_choices = []
105
+ chunk_delta = Delta()
106
+ if "role" in message:
107
+ chunk_delta.role = message["role"]
108
+ if "content" in message:
109
+ chunk_delta.content = message["content"]
110
+ chunk_choices.append(StreamingChoices(index=str(index), delta=chunk_delta))
111
+
112
+ chunk_usage = Usage()
113
+ chunk_response = ModelResponse(
114
+ id=conversation_id,
115
+ choices=chunk_choices,
116
+ context=chunk_context,
117
+ created=int(time.time()),
118
+ model=model,
119
+ usage=chunk_usage if chunk_usage else None,
120
+ stream=True
121
+ )
122
+ index += 1
123
+ yield chunk_response
124
+
125
+ def create_model_response_wrapper(self, result, model):
126
+ choices = []
127
+ context = []
128
+
129
+ result_dict = result.json()
130
+ messages = result_dict.get('messages', [])
131
+ results = []
132
+ cotext_pre = []
133
+
134
+ print("messages is:",messages)
135
+
136
+ for message in messages:
137
+ # 根据消息类型处理数据
138
+ if message['role'] == 'assistant' and message['type'] == 'knowledge':
139
+ # 解析知识型内容
140
+ content = message['content']
141
+ print("content is:",content)
142
+
143
+ # 假设knowledge类型内容是由"---\nrecall slice X:\n"分隔的
144
+ slices = content.split('---\n')
145
+ for slice in slices:
146
+ if slice.strip().startswith('recall slice'):
147
+ # 去掉前缀找到JSON部分
148
+ json_part = slice.strip().split('\n', 1)[-1].strip()
149
+ # 如果不是以\"}结尾,则强制添加
150
+ if not json_part.endswith('\"}'):
151
+ json_part += '\"}'
152
+
153
+ try:
154
+ # 尝试解析JSON数据
155
+ recall_data = json.loads(json_part)
156
+ cotext_pre.append(str(recall_data))
157
+ except json.JSONDecodeError:
158
+ raise CozeAIError(status_code=500, message=f"Error decoding JSON from slice: {json_part}")
159
+
160
+ elif message['role'] == 'assistant' and message['type'] == 'answer':
161
+ # 直接添加回复内容
162
+ results.append(message['content'])
163
+
164
+ message = Message(
165
+ content=results[0],
166
+ role="assistant"
167
+ )
168
+ choices.append(
169
+ Choices(
170
+ message=message,
171
+ index=0,
172
+ finish_reason="stop",
173
+ )
174
+ )
175
+
176
+ usage = Usage(
177
+ prompt_tokens=0,
178
+ completion_tokens=0,
179
+ total_tokens=0,
180
+ )
181
+
182
+ # Assume response_dict contains a field 'responseData' with quoteList to extract context
183
+ if cotext_pre:
184
+ i = 0
185
+ for context_item in cotext_pre:
186
+ i += 1
187
+ context.append(Context(id=i, content=context_item))
188
+
189
+ response = ModelResponse(
190
+ id=result_dict.get('conversation_id'),
191
+ choices=choices,
192
+ context=context,
193
+ created=int(time.time()),
194
+ model='',
195
+ usage=usage,
196
+ )
197
+ return response
198
+
199
+ def completion(self, model: str, messages: list, **kwargs):
200
+ try:
201
+ if model is None or messages is None:
202
+ raise CozeAIError(
203
+ status_code=422, message="Missing model or messages"
204
+ )
205
+ new_kwargs = self.pre_processing(**kwargs)
206
+ stream = kwargs.get("stream", False)
207
+
208
+ if stream:
209
+ return self.post_stream_processing_wrapper(model, messages, **new_kwargs)
210
+ else:
211
+ messages, query = self.to_formatted_prompt(messages)
212
+
213
+ # 接受user_id作为用户唯一身份标识传入参数
214
+ if 'user_id' not in kwargs:
215
+ user_id = generate_unique_uid()
216
+ else:
217
+ user_id = kwargs['user_id']
218
+ payload = json.dumps({"query": query,"user": user_id,"bot_id": model,"chat_history":[],"steam":False})
219
+
220
+ headers = {
221
+ "Authorization": f"Bearer {self.api_key}",
222
+ "Content-Type": "application/json",
223
+ }
224
+ result = requests.post(self.endpoint_url, headers=headers, data=payload)
225
+ return self.create_model_response_wrapper(result, model=model)
226
+ except Exception as e:
227
+ if hasattr(e, "status_code"):
228
+ raise CozeAIError(status_code=e.status_code, message=str(e))
229
+ else:
230
+ raise CozeAIError(status_code=500, message=str(e))
@@ -0,0 +1,192 @@
1
+ import json
2
+ from .base_provider import BaseProvider
3
+ from cnlitellm.utils import ModelResponse, Message, Choices, Usage, Context, Delta, StreamingChoices
4
+ import requests
5
+ import logging, json, time
6
+
7
+ class DifyOpenAIError(Exception):
8
+ def __init__(
9
+ self,
10
+ status_code,
11
+ message,
12
+ ):
13
+ self.status_code = status_code
14
+ self.message = message
15
+ super().__init__(self.message)
16
+
17
+ class DifyAIProvider(BaseProvider):
18
+ def __init__(self, **model_kwargs):
19
+ self.api_key = model_kwargs.get("api_key")
20
+ self.endpoint_url = "https://api.dify.ai/v1/chat-messages"
21
+
22
+ def pre_processing(self, **kwargs):
23
+ supported_params = [
24
+ "model", "messages", "max_tokens", "temperature", "top_p", "n",
25
+ "logprobs", "stream", "stop", "presence_penalty", "frequency_penalty",
26
+ "best_of", "logit_bias"
27
+ ]
28
+ for key in list(kwargs.keys()):
29
+ if key not in supported_params:
30
+ kwargs.pop(key)
31
+ return kwargs
32
+
33
+ def to_formatted_prompt(self, messages):
34
+ message = messages[-1]
35
+ # if last message role is not user, return error
36
+ if message["role"] != "user":
37
+ raise DifyOpenAIError(
38
+ status_code=422, message=f"Last message role should be user"
39
+ )
40
+ query = message["content"]
41
+ return messages, query
42
+
43
+
44
+ def post_stream_processing_wrapper(self, model, messages, **new_kwargs):
45
+ # 预处理对话内容并返回最近的用户问题
46
+ messages, query = self.to_formatted_prompt(messages)
47
+ mode = "streaming"
48
+ payload = json.dumps({"query": query, "response_mode": mode, "user": "abc-123","conversation_id": "","inputs":{}})
49
+
50
+ headers = {
51
+ "Authorization": f"Bearer {self.api_key}",
52
+ "Content-Type": "application/json",
53
+ }
54
+ response = requests.post(self.endpoint_url, headers=headers, data=payload)
55
+
56
+ chunk_usage = Usage()
57
+ index = 0
58
+ for line in response.iter_lines():
59
+ if line:
60
+ chunk_choices = []
61
+ chunk_context = []
62
+ chunk_delta = Delta()
63
+ # judge if the new_line begins with "data:"
64
+ if line.startswith(b"data:"):
65
+ new_line = line.decode("utf-8").replace("data: ", "")
66
+ if new_line == "[DONE]":
67
+ continue
68
+
69
+
70
+ data = json.loads(new_line)
71
+ print("data is:",data)
72
+ event = data.get("event")
73
+ msg_id = data.get("message_id")
74
+ if event == "message":
75
+ if 'answer' in data:
76
+ chunk_message = data["answer"]
77
+ if chunk_message:
78
+ chunk_delta.role = "assistant"
79
+ chunk_delta.content = chunk_message
80
+ chunk_choices.append(StreamingChoices(index=str(index), delta=chunk_delta))
81
+
82
+ if event == "message_end" and "metadata" in data:
83
+ metadata = data["metadata"]
84
+ if "usage" in metadata:
85
+ usage_info = metadata["usage"]
86
+ if "prompt_tokens" in usage_info:
87
+ chunk_usage.prompt_tokens = usage_info["prompt_tokens"]
88
+ if "completion_tokens" in usage_info:
89
+ chunk_usage.completion_tokens = usage_info["completion_tokens"]
90
+ if "total_tokens" in usage_info:
91
+ chunk_usage.total_tokens = usage_info["total_tokens"]
92
+
93
+ if 'retriever_resources' in metadata:
94
+ for resource in metadata['retriever_resources']:
95
+ chunk_context.append({
96
+ "id": resource["position"],
97
+ "content": resource["content"],
98
+ "score": resource["score"],
99
+ })
100
+ chunk_response = ModelResponse(
101
+ id=msg_id,
102
+ choices=chunk_choices,
103
+ context=chunk_context,
104
+ created=int(time.time()),
105
+ model=model,
106
+ usage=chunk_usage,
107
+ stream=True
108
+ )
109
+ index += 1
110
+ yield chunk_response
111
+
112
+ def create_model_response_wrapper(self, result, model):
113
+
114
+ print("result of dify:", result.json())
115
+
116
+ response_dict = result.json()
117
+ choices = []
118
+ context = []
119
+
120
+ message = Message(
121
+ content=response_dict['answer'], role="assistant"
122
+ )
123
+ choices.append(
124
+ Choices(
125
+ message=message,
126
+ index=0,
127
+ finish_reason="stop",
128
+ )
129
+ )
130
+
131
+ usage = Usage(
132
+ prompt_tokens=response_dict['metadata']["usage"]["prompt_tokens"],
133
+ completion_tokens=response_dict['metadata']["usage"]["completion_tokens"],
134
+ total_tokens=response_dict['metadata']["usage"]["total_tokens"],
135
+ )
136
+
137
+ if 'retriever_resources' in response_dict['metadata']:
138
+ retrieved_resources = response_dict['metadata']["retriever_resources"]
139
+ if retrieved_resources is not None:
140
+ for resource in retrieved_resources:
141
+ context.append(
142
+ Context(
143
+ id=resource["position"],
144
+ content=resource["content"],
145
+ score=resource["score"],
146
+ )
147
+ )
148
+ else:
149
+ context = []
150
+
151
+ response = ModelResponse(
152
+ id=response_dict["id"],
153
+ choices=choices,
154
+ context=context,
155
+ created=int(time.time()),
156
+ model=model,
157
+ usage=usage,
158
+ )
159
+ return response
160
+
161
+
162
+ def completion(self, model: str, messages: list, **kwargs):
163
+ try:
164
+ if model is None or messages is None:
165
+ raise DifyOpenAIError(
166
+ status_code=422, message=f"Missing model or messages"
167
+ )
168
+ new_kwargs = self.pre_processing(**kwargs)
169
+ stream = kwargs.get("stream", False)
170
+
171
+ print("stream is:", stream)
172
+
173
+ if stream:
174
+ return self.post_stream_processing_wrapper(model, messages, **new_kwargs)
175
+ else:
176
+ messages, query = self.to_formatted_prompt(messages)
177
+ mode = "blocking"
178
+
179
+ print("mode is:", mode)
180
+
181
+ payload = json.dumps({"query": query, "response_mode": mode, "user": "abc-123","conversation_id": "","inputs":{}})
182
+ headers = {
183
+ "Authorization": f"Bearer {self.api_key}",
184
+ "Content-Type": "application/json",
185
+ }
186
+ result = requests.post(self.endpoint_url, headers=headers, data=payload)
187
+ return self.create_model_response_wrapper(result, model=model)
188
+ except Exception as e:
189
+ if hasattr(e, "status_code"):
190
+ raise DifyOpenAIError(status_code=e.status_code, message=str(e))
191
+ else:
192
+ raise DifyOpenAIError(status_code=500, message=str(e))