随笔 - 22  文章 - 0  评论 - 0  阅读 - 973

stream json output

Summary of Stream JSON parser

通过分析 Promptulate 项目的 issue,对问题进行了深入探讨并给出了相应的处理方案。本文将详细描述该问题的处理过程。

issues目标

  1. 原文如下

    Currently, we cannot enable stream if setting output schema,
    so Need to build a json parser to parse stream type json data like this:
    img

  2. 解读

    对于 Promptulate 中的 Agent 输出,通常在指定输出格式(Output Formatter)后,只有在 Agent 输出完成后才能得到最终结果。
    然而,当输出格式中的某些字段(Field)难以快速生成时,用户可能需要等待较长时间。因此,我们希望在输出过程中,将结果以流的形式逐步输出。

预期结果

如上图所示,我们希望在流式输出过程中,已生成的字段按格式输出,而尚未生成的字段标记为 None。

最终目标:用户可以指定 Output Formatter 并流式输出结果,且可以对结果进行动态处理。

img

处理流程

1. 开始的时候,存在理解的错误。

误解

起初认为需要在输出多个 Output Formatter 对象时,将这些对象一个一个地流式输出。

在指定 Output Formatter 且 stream=True 的情况下,默认情况下会输出如下格式的流式数据:
img
img

目前,agent在指定Output Formatter以及steam=true的情况下,默认会输出以下格式的流式数据

class LLMBaseResponse(BaseModel):
    province: str = Field(description="province in China")
    capital: str = Field(description="Capital of the province")

class LLMResponse(BaseModel):
    provinces: List[LLMBaseResponse] = Field(description="List of provinces and capitals in China")

response = pne.chat(
    api_key=DeepKey,
    messages="Please tell me all provinces and Capital in China.",
    output_schema=LLMResponse,
    model="deepseek/deepseek-chat",
    stream=True,
    )

输入如下:

    ```json
        {
            {province='Sichuan', capital='Chengdu'},
            {province='Liaoning', capital='Shenyang'},
            ……
        }
    ```

这样的话,我们只需要对最外层的对象进行正则匹配。再逐一把内部对象匹配输出就行。

2. Review 反馈

我们需要在流式结果输出的过程中,展示单个json对象的输出流程。
并在没有匹配成功时通过None进行表示

期望结果如下:
img

3. 将目标进行分解

为了实现这个目标,我们主要应该考虑两点问题。

  1. JSON 不完整情况下的处理:

    如何将不完整的 JSON 数据补全为完整的 JSON 对象,并转换为指定的 Output Formatter。

  2. 处理 None 值:

    如何将 None 值添加到 Output Formatter 字段中,并解决 Pydantic 中不接受 None 的字段报错问题。

        # 产生如下报错
        pydantic_core._pydantic_core.ValidationError: 1 validation error for LLMBaseResponse
        capital
        Input should be a valid string [type=string_type, input_value=None, input_type=NoneType]
        For further information visit https://errors.pydantic.dev/2.7/v/string_type
    

4.解决方案:

  1. JSON 修复:

    参考开源项目 half-json,该项目对 JSONDecoderError(JSON 解析失败的报错)进行了封装和处理,可以有效修复大部分不完整的 JSON 数据。

    以此,我们对比一下修复前后的情况

        修复前: {
        修复后: {}
        修复前: {
    
        修复后: {
        }
        修复前: {
        "
        修复后: {
        "":null}
        修复前: {
        "province
        修复后: {
        "province":null}
        修复前: {
        "province":
        修复后: {
        "province":null}
        修复前: {
        "province": "
        修复后: {
        "province": ""}
        修复前: {
        "province": "Beijing",
    
        修复后: {
        "province": "Beijing",
        "}":null}
        修复前: {
        "province": "Beijing",
        "
        修复后: {
        "province": "Beijing",
        "":null}
        修复前: {
        "province": "Beijing",
        "capital
        修复后: {
        "province": "Beijing",
        "capital":null}
        修复前: {
        "province": "Beijing",
        "capital":
        修复后: {
        "province": "Beijing",
        "capital":null}
        修复前: {
        "province": "Beijing",
        "capital": "
        修复后: {
        "province": "Beijing",
        "capital": ""}
        修复前: {
        "province": "Beijing",
        "capital": "Be
        修复后: {
        "province": "Beijing",
        "capital": "Be"}
        修复前: {
        "province": "Beijing",
        "capital": "Beijing
        修复后: {
        "province": "Beijing",
        "capital": "Beijing"}
    

    可以看出,修复完成后的json格式数据,已经可以转化为Output Formatter的结果了。

  2. 使用 Optional 处理 None 值:

    使用 Optional 对 Pydantic 模型进行封装,允许字段接受 None 值。

    流程如下

        def change_model(model: BaseModel) -> BaseModel:
            """change pydantic model to BaseModel
    
            Args:
                model (BaseModel): pydantic model
    
            Returns:
                BaseModel: BaseModel
            """
            annotations = {
                field: Optional[type_] for field, type_ in model.__annotations__.items()
            }
            new_model = type(model.__name__, (BaseModel,), {"__annotations__": annotations})
            return new_model
    

    这样我们得到的值就可以进行转化了

最终结果

通过 pydantic BaseModel 随时获取流式输出的对象的数据。
img
结果如下:
img

具体可以参考promptlate官方文档:https://www.promptulate.cn/#/use_cases/chat_usage?id=stream-json-parser

展望如下

上方解释了整个issues的完成过程,可是,同样的也存在一些问题。

  1. json数据

    • 数字的支持比较弱 --> -02 / 0. / .0

    • 还不支持分支回溯 --> [{]

    • 突然想到, 应该反思一下, 这个是一个fixer, 而不是一个将任何字符串都转为 json 的东西 应该明确 JSONFixer 的能力范围, 对 runratio.sh 也应该比较前后两个的 json 相似程度。 (听起来像无能者的辩白?)

    • 也需要吧 parser 也做成 stack 这样可以解决 ["a] --> ["a"] 这样的 case

    • 考虑分支回溯的方式来试探

    • 解析缺失的 JSON 常量

  2. 由于在进行这样的做法时,返回的response与设置的Output Formatter不一致。

    这样会产生什么样的问题呢,如果我需要对流失产生的数据转换会原来的Output Formatter的话,会产生报错。

    举出具体的例子的话,就像这样:

    class LLMBaseResponse(BaseModel):
        province: str = Field(None,description="province in China")
        capital: str = Field(description="Capital of the province")
    
    response = pne.chat(
        api_key=DeepKey,
        messages="Please tell me a provinces and Capital in China.",
        output_schema=LLMBaseResponse,
        model="deepseek/deepseek-chat",
        stream=True,
    )
    
    for i in response:
        print(i.province,i.capital)
        # 条件转换
        di_t = {}
        if i.province == None:
            di_t["province"] = "Unknown"
        if i.capital == None:
            di_t["capital"] = "Unknown"
        response = LLMBaseResponse(province=i.province,capital=i.capital)
        print(response)
        # 直接转换
        response = LLMBaseResponse(province=i.province,capital=i.capital)
        print(response)
    

    报错如下
    img

posted on   longsihua2003  阅读(26)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5

点击右上角即可分享
微信分享提示