from config.logger import setup_logging from http import HTTPStatus import dashscope from dashscope import Application from core.providers.llm.base import LLMProviderBase from core.utils.util import check_model_key import time TAG = __name__ logger = setup_logging() class LLMProvider(LLMProviderBase): def __init__(self, config): self.api_key = config["api_key"] self.app_id = config["app_id"] self.base_url = config.get("base_url") self.is_No_prompt = config.get("is_no_prompt") self.memory_id = config.get("ali_memory_id") self.streaming_chunk_size = config.get("streaming_chunk_size", 3) # 每次流式返回的字符数 check_model_key("AliBLLLM", self.api_key) def response(self, session_id, dialogue): # 处理dialogue if self.is_No_prompt: dialogue.pop(0) logger.bind(tag=TAG).debug( f"【阿里百练API服务】处理后的dialogue: {dialogue}" ) # 构造调用参数 call_params = { "api_key": self.api_key, "app_id": self.app_id, "session_id": session_id, "messages": dialogue, # 开启SDK原生流式 "stream": True, } if self.memory_id != False: # 百练memory需要prompt参数 prompt = dialogue[-1].get("content") call_params["memory_id"] = self.memory_id call_params["prompt"] = prompt logger.bind(tag=TAG).debug( f"【阿里百练API服务】处理后的prompt: {prompt}" ) # 可选地设置自定义API基地址(若配置为兼容模式URL则忽略) if self.base_url and ("/api/" in self.base_url): dashscope.base_http_api_url = self.base_url responses = Application.call(**call_params) # 流式处理(SDK在stream=True时返回可迭代对象;否则返回单次响应对象) logger.bind(tag=TAG).debug( f"【阿里百练API服务】构造参数: {dict(call_params, api_key='***')}" ) last_text = "" try: for resp in responses: if resp.status_code != HTTPStatus.OK: logger.bind(tag=TAG).error( f"code={resp.status_code}, message={resp.message}, 请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code" ) continue current_text = getattr(getattr(resp, "output", None), "text", None) if current_text is None: continue # SDK流式为增量覆盖,计算差量输出 if len(current_text) >= len(last_text): delta = current_text[len(last_text):] else: # 避免偶发回退 delta = current_text if delta: yield delta last_text = current_text except TypeError: # 非流式回落(一次性返回) if responses.status_code != HTTPStatus.OK: logger.bind(tag=TAG).error( f"code={responses.status_code}, message={responses.message}, 请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code" ) yield "【阿里百练API服务响应异常】" else: full_text = getattr(getattr(responses, "output", None), "text", "") logger.bind(tag=TAG).info( f"【阿里百练API服务】完整响应长度: {len(full_text)}" ) for i in range(0, len(full_text), self.streaming_chunk_size): chunk = full_text[i:i + self.streaming_chunk_size] if chunk: yield chunk def response_with_functions(self, session_id, dialogue, functions=None): # 阿里百练当前未支持原生的 function call。为保持兼容,这里回退到普通文本流式输出。 # 上层会按 (content, tool_calls) 的形式消费,这里始终返回 (token, None) logger.bind(tag=TAG).warning( "阿里百练未实现原生 function call,已回退为纯文本流式输出" ) for token in self.response(session_id, dialogue): yield token, None