DB_GPT 源码分析(二)

DB_GPT 源码分析

概要

前些日子,我在博客上写了关于db_gpt中有关excel对话的内容,那是我的初次梳理,感觉还是有点生硬,在随着我的不断跟进,又有了新的体会和感受,在看之前梳理的博客,感觉对于研究源码来说还是不太友好,所以我又再次梳理了一次有关这个部分的内容,希望这次可以帮到大家。

当然如果有需要看与数据库对话的宝子们可以看我之前写的博客chat_with_db

因为我个人觉得这个基于excel对话的功能还是挺不错得,对于模型的要求也不算高,这里我用的是Qwen2.5的32B,特别有应用价值,所以想在这里分享给大家。

功能介绍

dbgpt_excel

针对于chat_excel部分,可以分为两块内容

在看了上面的视频之后,我们可以发现对于excel对话内容其实是分为两块内容:

  1. excel文件上传
  2. 基于上传的文件进行问答

excel文件上传

python 复制代码
@router.post("/v1/chat/mode/params/file/load")
async def params_load(
    conv_uid: str,  # 会话唯一标识
    chat_mode: str,  # 聊天模式
    model_name: str,  # 模型名称
    user_name: Optional[str] = None,  # 用户名称(可选)
    sys_code: Optional[str] = None,  # 系统代码(可选)
    doc_file: UploadFile = File(...),  # 上传的文件
):
    print(f"params_load: {conv_uid},{chat_mode},{model_name}")
    try:
        if doc_file:
            # 保存上传的文件
            upload_dir = os.path.join(KNOWLEDGE_UPLOAD_ROOT_PATH, chat_mode)  # 构建上传目录路径
            os.makedirs(upload_dir, exist_ok=True)  # 创建目录(如果不存在)
            upload_path = os.path.join(upload_dir, doc_file.filename)  # 构建文件上传路径
            print('文件上传地址' + upload_path)
            async with aiofiles.open(upload_path, "wb") as f:  # 以二进制写入模式打开文件
                await f.write(await doc_file.read())  # 异步写入上传的文件内容

            # 准备聊天会话
            dialogue = ConversationVo(
                conv_uid=conv_uid,  # 会话唯一标识
                chat_mode=chat_mode,  # 聊天模式
                select_param=doc_file.filename,  # 选择的文件名
                model_name=model_name,  # 模型名称
                user_name=user_name,  # 用户名称
                sys_code=sys_code,  # 系统代码
            )
            # 创建聊天实例并准备聊天
            chat: BaseChat = await get_chat_instance(dialogue)
            resp = await chat.prepare()  # 准备聊天内容

            print('-------------------------')
            rres = Result.succ(get_hist_messages(conv_uid))  # 获取会话历史消息
            print(rres)
        # 刷新消息
        return rres
    except Exception as e:
        logger.error("excel load error!", e)  # 记录错误日志
        return Result.failed(code="E000X", msg=f"File Load Error {str(e)}")  # 返回失败信息

有excel_reader参数,读取了excel的内容

chat: BaseChat = await get_chat_instance(dialogue)

对于这块内容我们需要alt+左键点击进去再看get_chat_instance方法

python 复制代码
async def get_chat_instance(dialogue: ConversationVo = Body()) -> BaseChat:
    logger.info(f"get_chat_instance:{dialogue}")  # 记录获取聊天实例的日志
    if not dialogue.chat_mode:
        dialogue.chat_mode = ChatScene.ChatNormal.value()  # 如果没有聊天模式,默认设置为正常模式
    if not dialogue.conv_uid:
        # 如果没有会话唯一标识,创建一个新的会话
        conv_vo = __new_conversation(
            dialogue.chat_mode, dialogue.user_name, dialogue.sys_code
        )
        dialogue.conv_uid = conv_vo.conv_uid  # 将新会话的唯一标识赋值给对话对象

    # 验证聊天模式是否有效
    if not ChatScene.is_valid_mode(dialogue.chat_mode):
        raise StopAsyncIteration(f"Unsupported Chat Mode,{dialogue.chat_mode}!")  # 如果无效,抛出异常

    # 准备聊天参数
    chat_param = {
        "chat_session_id": dialogue.conv_uid,  # 会话唯一标识
        "user_name": dialogue.user_name,  # 用户名称
        "sys_code": dialogue.sys_code,  # 系统代码
        "current_user_input": dialogue.user_input,  # 当前用户输入
        "select_param": dialogue.select_param,  # 选择的参数
        "model_name": dialogue.model_name,  # 模型名称
    }
    
    # 异步调用阻塞函数以获取聊天实现
    chat: BaseChat = await blocking_func_to_async(
        get_executor(),  # 获取执行器
        CHAT_FACTORY.get_implementation,  # 获取聊天实现的方法
        dialogue.chat_mode,  # 聊天模式
        **{"chat_param": chat_param},  # 聊天参数
    )
    return chat  # 返回聊天实例

其中CHAT_FACTORY.get_implementation, 是获取chat实例的方法

python 复制代码
class ChatFactory(metaclass=Singleton):
    @staticmethod
    def get_implementation(chat_mode, **kwargs):
        # 懒加载(Lazy loading)
        from dbgpt.app.scene.chat_dashboard.chat import ChatDashboard  # 导入聊天仪表盘类
        from dbgpt.app.scene.chat_dashboard.prompt import prompt  # 导入提示类
        from dbgpt.app.scene.chat_data.chat_excel.excel_analyze.chat import ChatExcel  # 导入Excel聊天类
        ...
        
        # 获取所有的聊天类(从BaseChat继承的类)
        chat_classes = BaseChat.__subclasses__()
        implementation = None  # 初始化实现变量
        for cls in chat_classes:
            # 检查当前类的聊天场景是否与请求的聊天模式匹配
            if cls.chat_scene == chat_mode:
                metadata = {"cls": str(cls)}  # 准备元数据以供追踪
                with root_tracer.start_span(
                    "get_implementation_of_chat", metadata=metadata  # 开始追踪
                ):
                    implementation = cls(**kwargs)  # 创建匹配类的实例
        if implementation is None:
            # 如果没有找到合适的实现,抛出异常
            raise Exception(f"Invalid implementation name:{chat_mode}")
        return implementation  # 返回找到的聊天实现

在implementation = cls(**kwargs)代码中,当根据chat_mode拿到对应的聊天的对象时候,自动会执行该类下的init方法,这里是拿到了ChatExcel类

python 复制代码
class ChatExcel(BaseChat):
    """一个用于分析Excel数据的类"""

    chat_scene: str = ChatScene.ChatExcel.value()  # 聊天场景类型
    keep_start_rounds = 1  # 保留的起始轮次
    keep_end_rounds = 2    # 保留的结束轮次

    def __init__(self, chat_param: Dict):
        """初始化Chat Excel模块
        Args:
           - chat_param: Dict
            - chat_session_id: (str) 聊天会话ID
            - current_user_input: (str) 当前用户输入
            - model_name: (str) LLM模型名称
            - select_param: (str) 文件路径
        """
        chat_mode = ChatScene.ChatExcel  # 设置聊天模式为Excel模式

        # 从chat_param中获取必要的参数
        self.select_param = chat_param["select_param"]  # 选择的参数(文件路径)
        self.model_name = chat_param["model_name"]      # 模型名称
        chat_param["chat_mode"] = ChatScene.ChatExcel  # 更新chat_param中的聊天模式

        # 检查选择的文件路径是否有效
        if has_path(self.select_param):
            # 如果路径有效,使用该路径创建Excel读取器
            self.excel_reader = ExcelReader(self.select_param)
        else:
            # 如果路径无效,构建完整路径并创建Excel读取器
            self.excel_reader = ExcelReader(
                os.path.join(
                    KNOWLEDGE_UPLOAD_ROOT_PATH, chat_mode.value(), self.select_param
                )
            )
        
        self.api_call = ApiCall()  # 初始化API调用对象
        super().__init__(chat_param=chat_param)  # 调用父类构造函数

在这里同样的ExcelReader(self.select_param)在创建ExcelReader对象的时候,会自动执行他的init方法,来读取excel的内容并存入chat的excel_reader属性中

python 复制代码
class ExcelReader:
    def __init__(self, file_path):
        # 获取文件名
        file_name = os.path.basename(file_path)  # 从文件路径中提取文件名
        self.file_name_without_extension = os.path.splitext(file_name)[0]  # 获取不带扩展名的文件名
        encoding, confidence = detect_encoding(file_path)  # 检测文件编码
        logger.info(f"Detected Encoding: {encoding} (Confidence: {confidence})")  # 记录编码信息
        self.excel_file_name = file_name  # 保存完整文件名
        self.extension = os.path.splitext(file_name)[1]  # 获取文件扩展名

        # 读取Excel文件
        if file_path.endswith(".xlsx") or file_path.endswith(".xls"):
            # 如果是Excel文件,读取数据
            df_tmp = pd.read_excel(file_path, index_col=False)  # 先读取一次以获取列数
            self.df = pd.read_excel(
                file_path,
                index_col=False,
                converters={i: csv_colunm_foramt for i in range(df_tmp.shape[1])},  # 设置转换器
            )
        elif file_path.endswith(".csv"):
            # 如果是CSV文件,读取数据
            df_tmp = pd.read_csv(file_path, index_col=False, encoding=encoding)  # 先读取一次以获取列数
            self.df = pd.read_csv(
                file_path,
                index_col=False,
                encoding=encoding,
                # csv_colunm_foramt 可以修改更多,只是针对美元人民币符号,假如是"你好¥¥¥"则会报错!
                converters={i: csv_colunm_foramt for i in range(df_tmp.shape[1])},  # 设置转换器
            )
        else:
            raise ValueError("Unsupported file format.")  # 抛出不支持的文件格式异常

        # 将空字符串替换为NaN
        self.df.replace("", np.nan, inplace=True)

        # 删除所有内容为空的"Unnamed"列
        unnamed_columns_tmp = [
            col
            for col in df_tmp.columns
            if col.startswith("Unnamed") and df_tmp[col].isnull().all()
        ]
        df_tmp.drop(columns=unnamed_columns_tmp, inplace=True)  # 删除这些列

        # 仅保留验证后的列
        self.df = self.df[df_tmp.columns.values]

        self.columns_map = {}  # 初始化列映射字典
        for column_name in df_tmp.columns:
            self.df[column_name] = self.df[column_name].astype(str)  # 将列转换为字符串类型
            self.columns_map.update({column_name: excel_colunm_format(column_name)})  # 更新列映射
            try:
                # 尝试将列转换为日期格式
                self.df[column_name] = pd.to_datetime(self.df[column_name]).dt.strftime(
                    "%Y-%m-%d"
                )
            except ValueError:
                try:
                    # 尝试将列转换为数值类型
                    self.df[column_name] = pd.to_numeric(self.df[column_name])
                except ValueError:
                    try:
                        # 如果失败,将列保持为字符串类型
                        self.df[column_name] = self.df[column_name].astype(str)
                    except Exception:
                        print("Can't transform column: " + column_name)  # 打印无法转换的列名

        # 清理列名,去除空格并替换为下划线
        self.df = self.df.rename(columns=lambda x: x.strip().replace(" ", "_"))

        # 连接DuckDB
        self.db = duckdb.connect(database=":memory:", read_only=False)  # 创建内存中的DuckDB连接

        self.table_name = "excel_data"  # 设置表名
        # 在DuckDB中写入数据
        self.db.register(self.table_name, self.df)

        # 获取结果并打印表结构信息
        result = self.db.execute(f"DESCRIBE {self.table_name}")  # 描述表结构
        columns = result.fetchall()  # 获取所有列信息
        for column in columns:
            print(column)  # 打印每列的信息

此时在我们的聊天对象实例中就有了excel的内容了,并且根据chat_model=chat_excel也确实是在聊天实例chat中添加了模板,过程如下

在class ChatFactory(metaclass=Singleton):中根据聊天场景匹配聊天实例的时候implementation = cls(**kwargs) # 创建匹配类的实例,这里是创建的ChatExcel对象,在其init初始化方法中调用了父类的init方法super().init (chat_param=chat_param)

在其父类中的初始化方法中有这么一段代码,根据聊天场景选择提示词模板

python 复制代码
self.prompt_template: AppScenePromptTemplateAdapter = (
            CFG.prompt_template_registry.get_prompt_template(
                self.chat_mode.value(),
                language=CFG.LANGUAGE,
                model_name=self.llm_model,
                proxyllm_backend=CFG.PROXYLLM_BACKEND,
            )
        )

之后再执行这段代码的时候

resp = await chat.prepare(),debug进入的是ChatExcel对象的prepare方法

python 复制代码
async def prepare(self):
    logger.info(f"{self.chat_mode} prepare start!")  # 记录准备开始的日志
    if self.has_history_messages():  # 检查是否有历史消息
        return None  # 如果有历史消息,直接返回None

    # 准备聊天参数
    chat_param = {
        "chat_session_id": self.chat_session_id,  # 当前聊天会话ID
        "user_input": "[" + self.excel_reader.excel_file_name + "]" + " Analyze!",  # 用户输入,包含Excel文件名
        "parent_mode": self.chat_mode,  # 当前聊天模式
        "select_param": self.excel_reader.excel_file_name,  # 选择的参数(Excel文件名)
        "excel_reader": self.excel_reader,  # Excel阅读器实例
        "model_name": self.model_name,  # 模型名称
    }

    # 创建Excel学习实例
    learn_chat = ExcelLearning(**chat_param)  
    result = await learn_chat.nostream_call()  # 异步调用学习方法
    return result  # 返回结果

learn_chat = ExcelLearning(**chat_param) 在创建ExcelLearning对象时候

python 复制代码
class ExcelLearning(BaseChat):
    chat_scene: str = ChatScene.ExcelLearning.value()

    def __init__(
        self,
        chat_session_id,
        user_input,
        parent_mode: Any = None,
        select_param: str = None,
        excel_reader: Any = None,
        model_name: str = None,
    ):
        chat_mode = ChatScene.ExcelLearning
        """ """
        self.excel_file_path = select_param
        self.excel_reader = excel_reader
        chat_param = {
            "chat_mode": chat_mode,
            "chat_session_id": chat_session_id,
            "current_user_input": user_input,
            "select_param": select_param,
            "model_name": model_name,
        }
        super().__init__(chat_param=chat_param)
        if parent_mode:
            self.current_message.chat_mode = parent_mode.value()

可以看到有如下的两行代码

chat_mode = ChatScene.ExcelLearning,

super().init(chat_param=chat_param)

重置了chat_model,调用了父类的构造方法,此时加载的提示词模板是

python 复制代码
_DEFAULT_TEMPLATE_ZH = """
下面是用户文件{file_name}的一部分数据,请学习理解该数据的结构和内容,按要求输出解析结果:
    {data_example}
分析各列数据的含义和作用,并对专业术语进行简单明了的解释, 如果是时间类型请给出时间格式类似:yyyy-MM-dd HH:MM:ss.
将列名作为属性名,分析解释作为属性值,组成json数组,并输出在返回json内容的ColumnAnalysis属性中.
请不要修改或者翻译列名,确保和给出数据列名一致.
针对数据从不同维度提供一些有用的分析思路给用户。

请一步一步思考,确保只以JSON格式回答,具体格式如下:
    {response}
"""

result = await learn_chat.nostream_call()

然后才调用的模型,所以最后用到的提示词是上面的这个,之前的excel_analyze中的提示词模板其实是没有用到的。

python 复制代码
async def nostream_call(self):
    # 构建模型请求的有效负载
    payload = await self._build_model_request()
    
    # 开始追踪
    span = root_tracer.start_span(
        "BaseChat.nostream_call", metadata=payload.to_dict()  # 记录请求的元数据
    )
    logger.info(f"Request: \n{payload}")  # 记录请求的有效负载信息
    payload.span_id = span.span_id  # 将span_id添加到有效负载中

    try:
        # 进行无流式调用,并重试
        ai_response_text, view_message = await self._no_streaming_call_with_retry(payload)
        
        # 添加AI返回的消息和视图消息到当前消息中
        self.current_message.add_ai_message(ai_response_text)
        self.current_message.add_view_message(view_message)
        
        # 调整消息
        self.message_adjust()
        span.end()  # 结束追踪
    except BaseAppException as e:
        # 如果捕获到应用程序异常,记录视图消息
        self.current_message.add_view_message(e.view)
        span.end(metadata={"error": str(e)})  # 结束追踪并记录错误信息
    except Exception as e:
        # 捕获其他异常,创建错误视图消息
        view_message = f"<span style='color:red'>ERROR!</span> {str(e)}"
        self.current_message.add_view_message(view_message)
        span.end(metadata={"error": str(e)})  # 结束追踪并记录错误信息

    # 存储当前会话
    await blocking_func_to_async(
        self._executor, self.current_message.end_current_round
    )
    
    # 获取当前AI响应
    rest = self.current_ai_response()
    return rest  # 返回响应

async def _no_streaming_call_with_retry(self, payload):
    with root_tracer.start_span("BaseChat.invoke_worker_manager.generate"):
        model_output = await self.call_llm_operator(payload)  # 调用 LLM 操作符,发送 payload 并接收模型输出

    ai_response_text = self.prompt_template.output_parser.parse_model_nostream_resp(
        model_output, self.prompt_template.sep  # 解析模型输出,生成 AI 响应文本
    )
    prompt_define_response = (
        self.prompt_template.output_parser.parse_prompt_response(ai_response_text)  # 解析生成的 AI 响应文本
    )
    metadata = {
        "model_output": model_output.to_dict(),  # 将模型输出转为字典形式保存
        "ai_response_text": ai_response_text,  # 保存 AI 响应文本
        "prompt_define_response": self._parse_prompt_define_response(
            prompt_define_response  # 解析提示定义的响应内容
        ),
    }
    print('base_chat.py--->' + metadata)  # 打印调试信息

    with root_tracer.start_span("BaseChat.do_action", metadata=metadata):
        result = await blocking_func_to_async(
            self._executor, self.do_action, prompt_define_response  # 执行指定的操作
        )

    speak_to_user = self.get_llm_speak(prompt_define_response)  # 获取用于与用户对话的文本

    view_message = await blocking_func_to_async(
        self._executor,
        self.prompt_template.output_parser.parse_view_response,
        speak_to_user,
        result,
        prompt_define_response,
    )
    return ai_response_text, view_message.replace("\n", "\\n")  # 返回 AI 响应和格式化的视图消息

chat with excel

python 复制代码
直接进对话框
@router.post("/v1/chat/completions")
async def chat_completions(
    dialogue: ConversationVo = Body(),
    flow_service: FlowService = Depends(get_chat_flow),
):

    #这里是选择的进行流式输出
return StreamingResponse(
                stream_generator(chat, dialogue.incremental, dialogue.model_name),
                headers=headers,
                media_type="text/plain",
            )  
python 复制代码
async def stream_generator(chat, incremental: bool, model_name: str):
    """
    生成流式响应。

    目标是生成兼容 OpenAI 的流式响应。
    当前增量响应是兼容的,未来将对完整响应进行转换。

    参数:
        chat (BaseChat): 聊天实例。
        incremental (bool): 用于控制内容是增量返回还是每次返回完整响应。
        model_name (str): 模型名称。

    产出:
        流式响应。
    """
    span = root_tracer.start_span("stream_generator")  # 开启追踪 span,记录执行时间等元数据
    msg = "[LLM_ERROR]: llm server has no output, maybe your prompt template is wrong."  # 错误信息的初始值

    previous_response = ""  # 记录上一次的响应内容,用于计算增量输出
    async for chunk in chat.stream_call():  # 异步调用 chat 的 stream_call 方法,逐块获取模型响应
        if chunk:  # 如果当前块有内容
            msg = chunk.replace("\ufffd", "")  # 移除可能的无效字符 (如 `\ufffd`)
            if incremental:  # 如果使用增量模式
                incremental_output = msg[len(previous_response) :]  # 计算本次增量内容
                choice_data = ChatCompletionResponseStreamChoice(
                    index=0,
                    delta=DeltaMessage(role="assistant", content=incremental_output),  # 将增量内容设置为响应体
                )
                chunk = ChatCompletionStreamResponse(
                    id=chat.chat_session_id, choices=[choice_data], model=model_name  # 创建兼容 OpenAI 的流式响应
                )
                json_chunk = model_to_json(
                    chunk, exclude_unset=True, ensure_ascii=False  # 将响应序列化为 JSON 格式
                )
                yield f"data: {json_chunk}\n\n"  # 将增量响应作为流式输出的一部分返回
            else:
                # TODO: 生成一个兼容 OpenAI 的完整流式响应
                msg = msg.replace("\n", "\\n")  # 处理换行符以确保格式兼容
                yield f"data:{msg}\n\n"  # 返回完整响应数据
            previous_response = msg  # 更新上一次的响应内容为当前响应
            await asyncio.sleep(0.02)  # 添加短暂延迟,模拟实时流式输出
    if incremental:  # 如果是增量模式,最后返回 `[DONE]` 表示流结束
        yield "data: [DONE]\n\n"
    span.end()  # 结束追踪 span
    
"""
功能:
	stream_generator 方法生成流式响应,适用于兼容 OpenAI 的增量返回模式。在增量模式下,每次仅返回与上次不同的新增内容;否则将返回整个响应。它通过异步生成器逐步返回数据,模拟实时响应的效果。
重要部分:
	增量模式计算新内容并返回。
	async for 循环用于逐块读取模型响应。
	使用短延迟来模拟连续的流式数据传输。
"""
python 复制代码
# 这个方法是 BaseChat 类中的方法,它负责向 LLM(大语言模型)发送请求并处理返回的流式响应。
async def stream_call(self):
    # TODO: 在服务器连接错误时重试
    payload = await self._build_model_request()  # 构建模型请求的 payload

    logger.info(f"payload request: \n{payload}")  # 记录请求日志信息
    ai_response_text = ""  # 初始化 AI 响应文本为空
    span = root_tracer.start_span(
        "BaseChat.stream_call", metadata=payload.to_dict()  # 开启 span,追踪请求的元数据
    )
    payload.span_id = span.span_id  # 将 span ID 赋值给请求的 payload
    try:
        async for output in self.call_streaming_operator(payload):  # 异步调用流式操作符,逐块接收模型输出
            # 结果生成时调用插件(如某种处理逻辑)
            msg = self.prompt_template.output_parser.parse_model_stream_resp_ex(
                output, 0  # 解析模型返回的流式响应
            )
            # 给出答案
            view_msg = self.stream_plugin_call(msg)  # 调用插件,处理响应内容
            view_msg = view_msg.replace("\n", "\\n")  # 将换行符替换为 "\\n" 以符合输出格式
            yield view_msg  # 逐块返回处理过的视图消息
        self.current_message.add_ai_message(msg)  # 将模型生成的响应文本添加到当前消息对象
        view_msg = self.stream_call_reinforce_fn(view_msg)  # 执行进一步的流式响应强化
        self.current_message.add_view_message(view_msg)  # 将最终生成的视图消息添加到当前消息对象
        span.end()  # 结束 span 追踪
    except Exception as e:  # 捕获异常
        print(traceback.format_exc())  # 打印异常堆栈信息
        logger.error("model response parse failed!" + str(e))  # 记录错误日志
        self.current_message.add_view_message(
            f"""<span style=\"color:red\">ERROR!</span>{str(e)}\n  {ai_response_text} """  # 展示错误信息
        )
        ### 存储当前对话
        span.end(metadata={"error": str(e)})  # 在 span 中记录错误元数据
    await blocking_func_to_async(
        self._executor, self.current_message.end_current_round  # 异步执行,结束当前消息轮次
    )
"""
 功能:stream_call 方法负责向 LLM 发送请求并异步接收流式响应。
        响应会通过插件进行处理,并以符合格式的视图消息形式返回。
        该方法还包含异常处理逻辑以应对可能的解析或网络错误。
重要部分:
    call_streaming_operator 方法用于异步接收流式数据。
    插件处理 stream_plugin_call 和强化 stream_call_reinforce_fn。
    详细的异常处理,捕获并展示错误信息。
    使用追踪 span 监控性能。
"""
python 复制代码
# 这个方法负责真正与模型的流式接口进行通信,发送请求并接收流式输出。
async def call_streaming_operator(self, request: ModelRequest) -> AsyncIterator[ModelOutput]:
    llm_task = build_cached_chat_operator(self.llm_client, True, CFG.SYSTEM_APP)  # 创建缓存的聊天操作符
    async for out in await llm_task.call_stream(call_data=request):  # 异步调用流式接口,发送请求并逐块接收输出
        yield out  # 逐块返回模型输出
"""
功能:call_streaming_operator 方法负责调用底层的语言模型接口,通过流式方式返回响应。它利用了异步生成器,将每一块数据逐步返回给调用者。
重要部分:
build_cached_chat_operator 创建了一个缓存的聊天操作符,用于减少重复请求的开销。
使用 async for 异步迭代器来逐步接收 LLM 的流式输出。
每次接收到模型输出时,使用 yield 返回给调用方,保证了响应的流式特性。
"""
相关推荐
极梦网络无忧7 小时前
OpenClaw 基础使用说明(中文版)
python
codeJinger8 小时前
【Python】操作Excel文件
python·excel
XLYcmy8 小时前
一个针对医疗RAG系统的数据窃取攻击工具
python·网络安全·ai·llm·agent·rag·ai安全
Islucas9 小时前
Claude code入门保姆级教程
python·bash·claude
萝卜白菜。9 小时前
TongWeb7.0相同的类指明加载顺序
开发语言·python·pycharm
赵钰老师9 小时前
【ADCIRC】基于“python+”潮汐、风驱动循环、风暴潮等海洋水动力模拟实践技术应用
python·信息可视化·数据分析
爬山算法9 小时前
MongoDB(80)如何在MongoDB中使用多文档事务?
数据库·python·mongodb
YuanDaima204810 小时前
基于 LangChain 1.0 的检索增强生成(RAG)实战
人工智能·笔记·python·langchain·个人开发·langgraph
ipython_harley10 小时前
【AGI】OpenAI核心贡献者翁家翌:修Infra的人,正在定义GPT-5
人工智能·gpt·ai·agi