1+ # encoding:utf-8
2+
3+ import time
4+ import json
5+ import openai
6+ import openai .error
7+ from bot .bot import Bot
8+ from bot .session_manager import SessionManager
9+ from bridge .context import ContextType
10+ from bridge .reply import Reply , ReplyType
11+ from common .log import logger
12+ from config import conf , load_config
13+ from .modelscope_session import ModelScopeSession
14+ import requests
15+
16+
17+ # ModelScope对话模型API
18+ class ModelScopeBot (Bot ):
19+ def __init__ (self ):
20+ super ().__init__ ()
21+ self .sessions = SessionManager (ModelScopeSession , model = conf ().get ("model" ) or "Qwen/Qwen2.5-7B-Instruct" )
22+ model = conf ().get ("model" ) or "Qwen/Qwen2.5-7B-Instruct"
23+ if model == "modelscope" :
24+ model = "Qwen/Qwen2.5-7B-Instruct"
25+ self .args = {
26+ "model" : model , # 对话模型的名称
27+ "temperature" : conf ().get ("temperature" , 0.3 ), # 如果设置,值域须为 [0, 1] 我们推荐 0.3,以达到较合适的效果。
28+ "top_p" : conf ().get ("top_p" , 1.0 ), # 使用默认值
29+ }
30+ self .api_key = conf ().get ("modelscope_api_key" )
31+ self .base_url = conf ().get ("modelscope_base_url" , "https://api-inference.modelscope.cn/v1/chat/completions" )
32+ """
33+ 需要获取ModelScope支持API-inference的模型名称列表,请到魔搭社区官网模型中心查看 https://modelscope.cn/models?filter=inference_type&page=1。
34+ 或者使用命令 curl https://api-inference.modelscope.cn/v1/models 对模型列表和ID进行获取。查看commend/const.py文件也可以获取模型列表。
35+ 获取ModelScope的免费API Key,请到魔搭社区官网用户中心查看获取方式 https://modelscope.cn/docs/model-service/API-Inference/intro。
36+ """
37+ def reply (self , query , context = None ):
38+ # acquire reply content
39+ if context .type == ContextType .TEXT :
40+ logger .info ("[MODELSCOPE_AI] query={}" .format (query ))
41+
42+ session_id = context ["session_id" ]
43+ reply = None
44+ clear_memory_commands = conf ().get ("clear_memory_commands" , ["#清除记忆" ])
45+ if query in clear_memory_commands :
46+ self .sessions .clear_session (session_id )
47+ reply = Reply (ReplyType .INFO , "记忆已清除" )
48+ elif query == "#清除所有" :
49+ self .sessions .clear_all_session ()
50+ reply = Reply (ReplyType .INFO , "所有人记忆已清除" )
51+ elif query == "#更新配置" :
52+ load_config ()
53+ reply = Reply (ReplyType .INFO , "配置已更新" )
54+ if reply :
55+ return reply
56+ session = self .sessions .session_query (query , session_id )
57+ logger .debug ("[MODELSCOPE_AI] session query={}" .format (session .messages ))
58+
59+ model = context .get ("modelscope_model" )
60+ new_args = self .args .copy ()
61+ if model :
62+ new_args ["model" ] = model
63+
64+ if new_args ["model" ] == "Qwen/QwQ-32B" :
65+ reply_content = self .reply_text_stream (session , args = new_args )
66+ else :
67+ reply_content = self .reply_text (session , args = new_args )
68+
69+ logger .debug (
70+ "[MODELSCOPE_AI] new_query={}, session_id={}, reply_cont={}, completion_tokens={}" .format (
71+ session .messages ,
72+ session_id ,
73+ reply_content ["content" ],
74+ reply_content ["completion_tokens" ],
75+ )
76+ )
77+ if reply_content ["completion_tokens" ] == 0 and len (reply_content ["content" ]) > 0 :
78+ # 只有当 content 为空且 completion_tokens 为 0 时才标记为错误
79+ if len (reply_content ["content" ]) == 0 :
80+ reply = Reply (ReplyType .ERROR , reply_content ["content" ])
81+ else :
82+ reply = Reply (ReplyType .TEXT , reply_content ["content" ])
83+ elif reply_content ["completion_tokens" ] > 0 :
84+ self .sessions .session_reply (reply_content ["content" ], session_id , reply_content ["total_tokens" ])
85+ reply = Reply (ReplyType .TEXT , reply_content ["content" ])
86+ else :
87+ reply = Reply (ReplyType .ERROR , reply_content ["content" ])
88+ logger .debug ("[MODELSCOPE_AI] reply {} used 0 tokens." .format (reply_content ))
89+ return reply
90+ elif context .type == ContextType .IMAGE_CREATE :
91+ ok , retstring = self .create_img (query , 0 )
92+ reply = None
93+ if ok :
94+ reply = Reply (ReplyType .IMAGE_URL , retstring )
95+ else :
96+ reply = Reply (ReplyType .ERROR , retstring )
97+ return reply
98+ else :
99+ reply = Reply (ReplyType .ERROR , "Bot不支持处理{}类型的消息" .format (context .type ))
100+ return reply
101+
102+ def reply_text (self , session : ModelScopeSession , args = None , retry_count = 0 ) -> dict :
103+ """
104+ call openai's ChatCompletion to get the answer
105+ :param session: a conversation session
106+ :param session_id: session id
107+ :param retry_count: retry count
108+ :return: {}
109+ """
110+ try :
111+ headers = {
112+ "Content-Type" : "application/json" ,
113+ "Authorization" : "Bearer " + self .api_key
114+ }
115+
116+ body = args
117+ body ["messages" ] = session .messages
118+ res = requests .post (
119+ self .base_url ,
120+ headers = headers ,
121+ data = json .dumps (body )
122+ )
123+
124+ if res .status_code == 200 :
125+ response = res .json ()
126+ return {
127+ "total_tokens" : response ["usage" ]["total_tokens" ],
128+ "completion_tokens" : response ["usage" ]["completion_tokens" ],
129+ "content" : response ["choices" ][0 ]["message" ]["content" ]
130+ }
131+ else :
132+ response = res .json ()
133+ if "errors" in response :
134+ error = response .get ("errors" )
135+ elif "error" in response :
136+ error = response .get ("error" )
137+ else :
138+ error = "Unknown error"
139+ logger .error (f"[MODELSCOPE_AI] chat failed, status_code={ res .status_code } , "
140+ f"msg={ error .get ('message' )} , type={ error .get ('type' )} " )
141+
142+ result = {"completion_tokens" : 0 , "content" : "提问太快啦,请休息一下再问我吧" }
143+ need_retry = False
144+ if res .status_code >= 500 :
145+ # server error, need retry
146+ logger .warn (f"[MODELSCOPE_AI] do retry, times={ retry_count } " )
147+ need_retry = retry_count < 2
148+ elif res .status_code == 401 :
149+ result ["content" ] = "授权失败,请检查API Key是否正确"
150+ elif res .status_code == 429 :
151+ result ["content" ] = "请求过于频繁,请稍后再试"
152+ need_retry = retry_count < 2
153+ else :
154+ need_retry = False
155+
156+ if need_retry :
157+ time .sleep (3 )
158+ return self .reply_text (session , args , retry_count + 1 )
159+ else :
160+ return result
161+ except Exception as e :
162+ logger .exception (e )
163+ need_retry = retry_count < 2
164+ result = {"completion_tokens" : 0 , "content" : "我现在有点累了,等会再来吧" }
165+ if need_retry :
166+ return self .reply_text (session , args , retry_count + 1 )
167+ else :
168+ return result
169+
170+ def reply_text_stream (self , session : ModelScopeSession , args = None , retry_count = 0 ) -> dict :
171+ """
172+ call ModelScope's ChatCompletion to get the answer with stream response
173+ :param session: a conversation session
174+ :param session_id: session id
175+ :param retry_count: retry count
176+ :return: {}
177+ """
178+ try :
179+ headers = {
180+ "Content-Type" : "application/json" ,
181+ "Authorization" : "Bearer " + self .api_key
182+ }
183+
184+ body = args
185+ body ["messages" ] = session .messages
186+ body ["stream" ] = True # 启用流式响应
187+
188+ res = requests .post (
189+ self .base_url ,
190+ headers = headers ,
191+ data = json .dumps (body ),
192+ stream = True
193+ )
194+ if res .status_code == 200 :
195+ content = ""
196+ for line in res .iter_lines ():
197+ if line :
198+ decoded_line = line .decode ('utf-8' )
199+ if decoded_line .startswith ("data: " ):
200+ try :
201+ json_data = json .loads (decoded_line [6 :])
202+ delta_content = json_data .get ("choices" , [{}])[0 ].get ("delta" , {}).get ("content" , "" )
203+ if delta_content :
204+ content += delta_content
205+ except json .JSONDecodeError as e :
206+ pass
207+ return {
208+ "total_tokens" : 1 , # 流式响应通常不返回token使用情况
209+ "completion_tokens" : 1 ,
210+ "content" : content
211+ }
212+ else :
213+ response = res .json ()
214+ if "errors" in response :
215+ error = response .get ("errors" )
216+ elif "error" in response :
217+ error = response .get ("error" )
218+ else :
219+ error = "Unknown error"
220+ logger .error (f"[MODELSCOPE_AI] chat failed, status_code={ res .status_code } , "
221+ f"msg={ error .get ('message' )} , type={ error .get ('type' )} " )
222+
223+ result = {"completion_tokens" : 0 , "content" : "提问太快啦,请休息一下再问我吧" }
224+ need_retry = False
225+ if res .status_code >= 500 :
226+ # server error, need retry
227+ logger .warn (f"[MODELSCOPE_AI] do retry, times={ retry_count } " )
228+ need_retry = retry_count < 2
229+ elif res .status_code == 401 :
230+ result ["content" ] = "授权失败,请检查API Key是否正确"
231+ elif res .status_code == 429 :
232+ result ["content" ] = "请求过于频繁,请稍后再试"
233+ need_retry = retry_count < 2
234+ else :
235+ need_retry = False
236+
237+ if need_retry :
238+ time .sleep (3 )
239+ return self .reply_text_stream (session , args , retry_count + 1 )
240+ else :
241+ return result
242+ except Exception as e :
243+ logger .exception (e )
244+ need_retry = retry_count < 2
245+ result = {"completion_tokens" : 0 , "content" : "我现在有点累了,等会再来吧" }
246+ if need_retry :
247+ return self .reply_text_stream (session , args , retry_count + 1 )
248+ else :
249+ return result
250+ def create_img (self , query , retry_count = 0 ):
251+ try :
252+ logger .info ("[ModelScopeImage] image_query={}" .format (query ))
253+ headers = {
254+ "Content-Type" : "application/json; charset=utf-8" , # 明确指定编码
255+ "Authorization" : f"Bearer { self .api_key } "
256+ }
257+ payload = {
258+ "prompt" : query , # required
259+ "n" : 1 ,
260+ "model" : conf ().get ("text_to_image" ),
261+ }
262+ url = "https://api-inference.modelscope.cn/v1/images/generations"
263+
264+ # 手动序列化并保留中文(禁用 ASCII 转义)
265+ json_payload = json .dumps (payload , ensure_ascii = False ).encode ('utf-8' )
266+
267+ # 使用 data 参数发送原始字符串(requests 会自动处理编码)
268+ res = requests .post (url , headers = headers , data = json_payload )
269+
270+ response_data = res .json ()
271+ image_url = response_data ['images' ][0 ]['url' ]
272+ logger .info ("[ModelScopeImage] image_url={}" .format (image_url ))
273+ return True , image_url
274+
275+ except Exception as e :
276+ logger .error (format (e ))
277+ return False , "画图出现问题,请休息一下再问我吧"
0 commit comments