实践体验之用 LangChain 打造鲜花文案生成器 | 豆包MarsCode AI刷题

75 阅读6分钟

人工智能应用开发正变得越来越贴近我们的日常工作,比如为电商平台自动生成吸引人的商品文案。而在实际开发中,如何高效调用大语言模型(LLM),如何确保输出满足需求,这些问题并没有现成的答案。在这篇文章中,我将结合用 LangChain 实现鲜花文案生成器的实践,和大家聊聊这个工具的技术特点、优势,以及我在使用过程中遇到的一些问题和思考。

LangChain:不仅是工具,更是开发加速器

技术上的优雅与灵活

我们经常会听到“抽象化”和“模块化”这些词,但在开发 LLM 应用时,它们的意义才真正被体现出来。LangChain 提供的模板管理、变量动态填充、输出解析器这些工具,简直就像给开发者装上了涡轮引擎。比如:

• 在提示模板管理上,LangChain 提供了清晰的变量动态注入和校验机制。只需要定义一次模板,后续的每次调用都变得有条不紊。而且,它还会提醒你模板里的变量是不是填全了,就像有个可靠的助手一直帮你校对代码。

• 模型切换的灵活性更是让我印象深刻。在项目中,我一开始使用 OpenAI 的 GPT-3.5 模型,后来尝试切换到豆包模型,仅仅是修改了模型名称和 API 地址,代码的其他部分完全不需要改动。要知道,在没有 LangChain 的时候,这种改动可能会牵一发动全身。

输出解析让模型更“懂事”

在开发过程中,我一直在思考一个问题:如何让模型的输出尽可能贴近我们希望的样子?毕竟,模型生成的内容是概率性的,有时候会“自作主张”。LangChain 的输出解析器(StructuredOutputParser)给了我答案。

通过 ResponseSchema,我们可以清晰地定义模型输出的字段和格式,比如这次文案生成需要返回“文案描述”和“设计理由”两个字段:

response_schemas = [
    ResponseSchema(name="description", description="鲜花的描述文案"),
    ResponseSchema(name="reason", description="为什么要这样写这个文案"),
]

然后 LangChain 会生成一段类似于“操作手册”的格式指引,明确告诉模型如何返回结果。这让我在解析模型返回内容时,能够轻松将其转换为字典对象,直接存入 DataFrame。

实践过程中的思考与问题

1. 为什么输出解析器能提高输出的结构化程度?

一开始,我对输出解析器的作用心存疑虑:语言模型真的会乖乖按照提示格式生成内容吗?直到我打印出提示模板,看到完整的指令内容:

您是一位专业的鲜花店文案撰写员。
对于售价为 50 元的玫瑰 ,您能提供一个吸引人的简短描述吗?
返回格式如下:
{
    "description": "string - 鲜花的描述文案",
    "reason": "string - 为什么要这样写这个文案"
}

这段“返回格式如下”的内容就是关键。它明确地告诉模型需要输出 JSON 格式的内容,甚至还解释了每个字段的含义。虽然模型是基于概率的生成过程,但提供足够清晰的指令,就像给司机画好地图,模型自然会更“听话”。

2. 输出格式总是完美的吗?

然而,在实际调用中,我还是遇到了一些问题。比如,有一次模型返回了不完整的 JSON 格式,或者遗漏了某些字段。这让我意识到,即便提供了格式指令,模型也无法保证 100% 遵循格式。这时的解决方法就是:冗余机制

我在代码中加入了 try-except 来捕获解析错误,同时记录原始输出内容。这不仅让我能分析问题,还为后续优化提示模板提供了依据:

try:
    parsed_output = output_parser.parse(output_content)
except ValueError as e:
    print(f"解析错误:{e}")

这种错误处理机制也让我更加确信:开发 LLM 应用,设计冗余机制是不可或缺的。

3. 提示设计的难点

另一个让我印象深刻的挑战,是如何设计一个既能生成好内容,又能减少误差的提示。大语言模型很强大,但对“提示指令”特别敏感。提示太长,模型可能迷失方向;提示太短,又可能理解不到位。在这个项目中,我几次调整提示的措辞,尝试用更简洁的表达,让模型专注于任务核心。

完整代码与实现细节

以下是完整代码,它包含了模板构建、模型调用、输出解析和错误处理等关键部分:

import os
import openai
from langchain.prompts import PromptTemplate
from langchain.output_parsers import StructuredOutputParser, ResponseSchema
import pandas as pd

# 设置环境变量
os.environ["OPENAI_API_KEY"] = "your_api_key"
os.environ["OPENAI_API_BASE"] = "https://your_api_base"
os.environ["LLM_MODELEND"] = "your_model_name"

openai.api_key = os.environ.get("OPENAI_API_KEY")
openai.api_base = os.environ.get("OPENAI_API_BASE")

# 定义输出格式
response_schemas = [
    ResponseSchema(name="description", description="鲜花的描述文案"),
    ResponseSchema(name="reason", description="为什么要这样写这个文案"),
]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)

# 定义提示模板
prompt_template = """您是一位专业的鲜花店文案撰写员。
对于售价为 {price} 元的 {flower_name} ,您能提供一个吸引人的简短描述吗?
{format_instructions}"""
prompt = PromptTemplate.from_template(
    prompt_template, partial_variables={"format_instructions": output_parser.get_format_instructions()}
)

# 数据准备
flowers = ["玫瑰", "百合", "康乃馨"]
prices = ["50", "30", "20"]
df = pd.DataFrame(columns=["flower", "price", "description", "reason"])

# 调用模型并解析输出
for flower, price in zip(flowers, prices):
    input_text = prompt.format(flower_name=flower, price=price)
    response = openai.ChatCompletion.create(
        model=os.environ.get("LLM_MODELEND"),
        messages=[
            {"role": "system", "content": "You are a helpful assistant."},
            {"role": "user", "content": input_text},
        ],
        max_tokens=200,
    )
    output_content = response.choices[0].message.content.strip()
    try:
        parsed_output = output_parser.parse(output_content)
        parsed_output["flower"] = flower
        parsed_output["price"] = price
        df.loc[len(df)] = parsed_output
    except ValueError as e:
        print(f"解析错误:{e}")

# 保存结果
df.to_csv("flowers_with_descriptions.csv", index=False)

运行后形成了csv文件:

image.png

总结:从工具到思维的转变

通过这次实践,我更深刻地体会到 LangChain 的意义——它不仅是一个工具,更是一种思维方式。在开发过程中,我逐渐意识到,写代码不仅仅是解决问题,更是不断优化提示、调整流程和提升容错性的过程。

当然,LangChain 并不是万能的,它对提示设计的依赖性很高,而且在输出解析中仍需要人为干预。但正是这种局限性,激发了我对提示工程、模型调优等领域的更多探索。

这就是 LangChain 带给我的,不只是高效开发,还有技术思考的乐趣。