wangzhibo
2026-10-08 220e4fab27b7f419b3e89e2f2d28ae789aac09d7
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
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