diff --git a/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py b/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py index 012035ee..a68c4348 100644 --- a/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py +++ b/main/xiaozhi-server/core/providers/llm/AliBL/AliBL.py @@ -1,8 +1,10 @@ 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() @@ -15,6 +17,7 @@ class LLMProvider(LLMProviderBase): 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): @@ -32,6 +35,8 @@ class LLMProvider(LLMProviderBase): "app_id": self.app_id, "session_id": session_id, "messages": dialogue, + # 开启SDK原生流式 + "stream": True, } if self.memory_id != False: # 百练memory需要prompt参数 @@ -42,25 +47,63 @@ class LLMProvider(LLMProviderBase): 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) - if responses.status_code != HTTPStatus.OK: - logger.bind(tag=TAG).error( - f"code={responses.status_code}, " - f"message={responses.message}, " - f"请参考文档:https://help.aliyun.com/zh/model-studio/developer-reference/error-code" - ) - yield "【阿里百练API服务响应异常】" - else: - logger.bind(tag=TAG).debug( - f"【阿里百练API服务】构造参数: {call_params}" - ) - yield responses.output.text + + # 流式处理(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 except Exception as e: logger.bind(tag=TAG).error(f"【阿里百练API服务】响应异常: {e}") yield "【LLM服务响应异常】" def response_with_functions(self, session_id, dialogue, functions=None): - logger.bind(tag=TAG).error( - f"阿里百练暂未实现完整的工具调用(function call),建议使用其他意图识别" + # 阿里百练当前未支持原生的 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