青蛙小白
博客 / 2025/03

LangChain OutputParser 与 Structured Output:OpenAI API请求格式对比

2025/03/20 · — 字 · 阅读约 — 分钟 ·
目录

在使用LangChain进行结构化数据抽取时,常见的两种方式分别是OutputParser和Structured Output。二者在调用OpenAI API时的请求报文和底层机制存在明显差异。本文通过具体代码示例,详细对比这两种方式的实现细节、API请求体、响应内容,并分析各自的优缺点和适用场景,可根据实际需求做出选择。

随着大语言模型(LLM)在信息抽取、结构化问答等场景的广泛应用,如何让模型输出符合预期结构的数据成为开发者关注的重点。 LangChain作为主流的LLM应用开发框架,提供了多种结构化输出方案,其中OutputParser和Structured Output是最常用的两种。

  • OutputParser:通过提示工程(Prompt Engineering)和解析器,将模型输出转换为指定的数据结构。
  • Structured Output:利用OpenAI API的原生response_format参数,直接要求模型生成符合JSON Schema的结构化数据。

下面分别介绍这两种方式及其在API求和响应中的表现。


OutputParser 示例及其OpenAI API请求报文

OutputParser方式通过在提示中明确指定输出格式要求,然后用解析器将模型输出转换为所需结构。适用于模型本身不支持原生结构化输出的场景,或需要自定义复杂格式时。

示例代码:

from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import PydanticOutputParser

import os

class RestaurantReview(BaseModel):
    """Restaurant review structure"""

    restaurant_name: str = Field(description="餐厅的名称")
    food_rating: int = Field(description="菜品口味评分,1-5分")
    service_rating: int = Field(description="服务质量评分,1-5分")
    ambiance_rating: int = Field(description="餐厅环境评分,1-5分")
    recommended_dishes: str = Field(description="推荐的特色菜品")
    overall_comment: str = Field(description="对餐厅的总体评价和建议")

def main():
    load_dotenv()
    model_name = os.getenv("MODEL_NAME")
    model = ChatOpenAI(model=model_name, temperature=0)

    output_parser = PydanticOutputParser(pydantic_object=RestaurantReview)
    prompt_template = ChatPromptTemplate.from_messages(
        [
            (
                "system",
                "你是一个专业的餐厅评价分析助手,能够从用户描述中提取结构化信息。",
            ),
            (
                "human",
                "请根据用户的描述,生成一个结构化的餐厅评价。\n{format_instructions}\n用户描述: {query}",
            ),
        ]
    )

    chain = prompt_template | model | output_parser
    review_result = chain.invoke(
        {
            "query": "我昨天去了一家叫'江南小厨'的餐厅,菜品很美味,尤其是红烧肉和清蒸鱼非常好吃。服务员态度一般,有时需要等很久才能叫到人。餐厅装修很有江南风格,环境优雅。总的来说还是值得一去的。",
            "format_instructions": output_parser.get_format_instructions(),
        }
    )

    assert isinstance(review_result, RestaurantReview)
    print(review_result)

if __name__ == "__main__":
    main()

请求LLM API的请求体:

 
 
 
 {
  "messages":[ {
    "content":"你是一个专业的餐厅评价分析助手,能够从用户描述中提取结构化信息。","role":"system"
  }
  , {
    "content":"请根据用户的描述,生成一个结构化的餐厅评价。\nThe output should be formatted as a JSON instance that conforms to the JSON schema below.\n\nAs an example, for the schema {\"properties\": {\"foo\": {\"title\": \"Foo\", \"description\": \"a list of strings\", \"type\": \"array\", \"items\": {\"type\": \"string\"}}}, \"required\": [\"foo\"]}\nthe object {\"foo\": [\"bar\", \"baz\"]} is a well-formatted instance of the schema. The object {\"properties\": {\"foo\": [\"bar\", \"baz\"]}} is not well-formatted.\n\nHere is the output schema:\n```\n{\"description\": \"Restaurant review structure\", \"properties\": {\"restaurant_name\": {\"description\": \"餐厅的名称\", \"title\": \"Restaurant Name\", \"type\": \"string\"}, \"food_rating\": {\"description\": \"菜品口味评分,1-5分\", \"title\": \"Food Rating\", \"type\": \"integer\"}, \"service_rating\": {\"description\": \"服务质量评分,1-5分\", \"title\": \"Service Rating\", \"type\": \"integer\"}, \"ambiance_rating\": {\"description\": \"餐厅环境评分,1-5分\", \"title\": \"Ambiance Rating\", \"type\": \"integer\"}, \"recommended_dishes\": {\"description\": \"推荐的特色菜品\", \"title\": \"Recommended Dishes\", \"type\": \"string\"}, \"overall_comment\": {\"description\": \"对餐厅的总体评价和建议\", \"title\": \"Overall Comment\", \"type\": \"string\"}}, \"required\": [\"restaurant_name\", \"food_rating\", \"service_rating\", \"ambiance_rating\", \"recommended_dishes\", \"overall_comment\"]}\n```\n用户描述: 我昨天去了一家叫'江南小厨'的餐厅,菜品很美味,尤其是红烧肉和清蒸鱼非常好吃。服务员态度一般,有时需要等很久才能叫到人。餐厅装修很有江南风格,环境优雅。总的来说还是值得一去的。","role":"user"
  }
  ],"model":"xxx","stream":false,"temperature":0.0
}

可以看到,对于OutputParser,通过在提示中明确指定输出格式要求,然后使用解析器将模型输出转换为所需的结构。

LLM API返回的响应:

 {
  "id":"chatcmpl-9fcb70a754484716aaf8f8eef9f97832",
  "object":"chat.completion",
  "model":"xxx",
  "choices":[ {
    "index":0,
    "message": {
      "role":"assistant",
      "reasoning_content":null,
      "content":"<think>\n好的,我需要根据用户的描述生成一个符合指定JSON结构的餐厅评价。首先,我要仔细阅读用户提供的信息,然后按照schema的要求提取各个字段。\n\n用户提到的餐厅名称是"江南小厨",这个直接填入restaurant_name。接下来是三个评分:菜品口味、服务质量和环境。用户说菜品很美味,所以food_rating应该给高分,比如5分。服务方面,用户提到服务员态度一般,有时需要等很久,所以service_rating可能给3分。环境方面,用户称赞装修有江南风格且优雅,所以ambiance_rating可以是4或5分,这里选5分。\n\n推荐的特色菜是红烧肉和清蒸鱼,需要合并成一个字符串,用逗号分隔。总体评价部分要总结用户的感受,包括正面和需要改进的地方,比如总体不错但服务可以提升。最后检查所有必填字段是否齐全,确保没有遗漏。确认每个字段的类型正确,特别是评分是整数,推荐菜品是字符串,总体评价也是字符串。完成后,生成正确的JSON结构。\n</think>\n\n```json\n{\n  \"restaurant_name\": \"江南小厨\",\n  \"food_rating\": 5,\n  \"service_rating\": 3,\n  \"ambiance_rating\": 5,\n  \"recommended_dishes\": \"红烧肉, 清蒸鱼\",\n  \"overall_comment\": \"餐厅菜品美味,环境优雅具有江南特色,值得推荐。但服务响应速度有待提升,建议加强员工培训。\"\n}\n```",
      "tool_calls":[]
    }
    ,"logprobs":null,
    "finish_reason":"stop",
    "stop_reason":null
  }
  ],"usage": {
    "prompt_tokens":437,"total_tokens":754,"completion_tokens":317,"prompt_tokens_details":null
  }
  ,"prompt_logprobs":null
}

示例代码的运行输出:

restaurant_name='江南小厨' food_rating=5 service_rating=3 ambiance_rating=5 recommended_dishes='红烧肉, 清蒸鱼' overall_comment='餐厅菜品美味,环境优雅具有江南特色,值得推荐。但服务响应速度有待提升,建议加强员工培训。'

Structured Output示例及其OpenAI API请求报文

Structured Output方式是OpenAI API的原生能力,通过response_format参数直接要求模型输出符合特定结构的数据。LangChain通过 .with_structured_output()方法简化了这一过程。

示例代码:

from dotenv import load_dotenv
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from langchain_core.prompts import ChatPromptTemplate

import os



class RestaurantReview(BaseModel):
    """Restaurant review structure"""

    restaurant_name: str = Field(description="餐厅的名称")
    food_rating: int = Field(description="菜品口味评分,1-5分")
    service_rating: int = Field(description="服务质量评分,1-5分")
    ambiance_rating: int = Field(description="餐厅环境评分,1-5分")
    recommended_dishes: str = Field(description="推荐的特色菜品")
    overall_comment: str = Field(description="对餐厅的总体评价和建议")


def main():
    load_dotenv()
    model_name = os.getenv("MODEL_NAME")
    model = ChatOpenAI(model=model_name, temperature=0).with_structured_output(RestaurantReview)

    prompt_template = ChatPromptTemplate.from_messages(
        [
            (
                "system",
                "你是一个专业的餐厅评价分析助手,能够从用户描述中提取结构化信息。",
            ),
            (
                "human",
                "请根据用户的描述,生成一个结构化的餐厅评价。\n用户描述: {query}",
            ),
        ]
    )

    chain = prompt_template | model
    review_result = chain.invoke(
        {
            "query": "我昨天去了一家叫'江南小厨'的餐厅,菜品很美味,尤其是红烧肉和清蒸鱼非常好吃。服务员态度一般,有时需要等很久才能叫到人。餐厅装修很有江南风格,环境优雅。总的来说还是值得一去的。",
        }
    )

    assert isinstance(review_result, RestaurantReview)
    print(review_result)


if __name__ == "__main__":
    main()

请求LLMAPI的请求体:

{
  "messages":[ {
    "content":"你是一个专业的餐厅评价分析助手,能够从用户描述中提取结构化信息。","role":"system"
  }
  , {
    "content":"请根据用户的描述,生成一个结构化的餐厅评价。\n用户描述: 我昨天去了一家叫'江南小厨'的餐厅,菜品很美味,尤其是红烧肉和清蒸鱼非常好吃。服务员态度一般,有时需要等很久才能叫到人。餐厅装修很有江南风格,环境优雅。总的来说还是值得一去的。","role":"user"
  }
  ],"model":"xxx",
  "response_format": {
    "type":"json_schema",
    "json_schema": {
      "schema": {
        "description":"Restaurant review structure","properties": {
          "restaurant_name": {
            "description":"餐厅的名称","title":"Restaurant Name","type":"string"
          }
          ,"food_rating": {
            "description":"菜品口味评分,1-5分","title":"Food Rating","type":"integer"
          }
          ,"service_rating": {
            "description":"服务质量评分,1-5分","title":"Service Rating","type":"integer"
          }
          ,"ambiance_rating": {
            "description":"餐厅环境评分,1-5分","title":"Ambiance Rating","type":"integer"
          }
          ,"recommended_dishes": {
            "description":"推荐的特色菜品","title":"Recommended Dishes","type":"string"
          }
          ,"overall_comment": {
            "description":"对餐厅的总体评价和建议","title":"Overall Comment","type":"string"
          }
          
        },
        "required":["restaurant_name","food_rating","service_rating","ambiance_rating","recommended_dishes","overall_comment"],
        "title":"RestaurantReview",
        "type":"object",
        "additionalProperties":false
      },
      "name":"RestaurantReview",
      "strict":true
    }
    
  }
  ,"stream":false,"temperature":0.0
}

可以看出,Structured output是大模型API的原生功能,通过API参数直接要求模型输出符合特定结构的内容。对于OPENAI API规范通过/v1/chat/completions端点的response_format参数实现,模型会在底层直接生成符合要求的结构化数据。

LLM API返回的响应:

 {
  "id":"chatcmpl-46ce47aafb7b4c7e8894c4b6d8690e1d",
  "object":"chat.completion",
  "model":"xxx",
  "choices":[ {
    "index":0,
    "message": {
      "role":"assistant",
      "reasoning_content":null,
      "content":"{\n\n\"restaurant_name\": \"江南小厨\",\n\"food_rating\": 5,\n\"service_rating\": 3,\n\"ambiance_rating\": 4,\n\"recommended_dishes\": \"红烧肉, 清蒸鱼\",\n\"overall_comment\": \"总的来说还是值得一去的\"\n}",
      "tool_calls":[]
    }
    ,"logprobs":null,
    "finish_reason":"stop",
    "stop_reason":null
  }
  ],"usage": {
    "prompt_tokens":104,"total_tokens":164,"completion_tokens":60,"prompt_tokens_details":null
  }
  ,"prompt_logprobs":null
}

示例代码的运行输出:

restaurant_name='江南小厨' food_rating=5 service_rating=3 ambiance_rating=4 recommended_dishes='红烧肉, 清蒸鱼' overall_comment='总的来说还是值得一去的'

对比分析与总结

方式格式约束方式API 支持适用场景
OutputParserPrompt+解析器通用兼容所有模型,格式灵活
Structured OutputAPI请求中的response_format参数OpenAI仅限支持response_format的模型
  • OutputParser 适合需要兼容多种模型或自定义复杂结构的场景,但对 prompt 设计和解析器健壮性要求较高。
  • Structured Output 则更适合追求开发效率和数据可靠性的场景,但依赖于模型和 API 的原生支持。

建议在LLM API支持的前提下优先使用Structured Output,其他情况可采用OutputParser 方案。

评论