LangChain快速筑基(带代码)P1-输入控制与输出解析

引言

在上一篇LangChain入门中,我们已经了解到了可以使用LangChain的langchain_deepseek组件与DeepSeek进行对话。 但DeepSeek并没有提供如OpenAI一样的诸多参数来解决AI使用的问题,如控制随机性的temperature,控制重复(质量和新颖度)的presence_penalty和frequency_penalty。

关于参数在之前常见AI参数讲解有介绍过。

那AI的诸多问题要怎么解决呢?要怎么控制大模型(AI)呢? 常见问题如下:

我们应该使用"直接修改大模型本身"之外的方案来解决问题(不现实)。 比如,LangChain。 LangChain不仅仅通过简单传递参数给 LLM API来解决问题,它还抽象了一系列组件来提供问题的解决方案。使用LangChain,可以讲不同大模型API封装成统一、易于调用的形式。

需要快速回顾和看懂Python的Java程序员可以看一下我之前写的Python快速入门

基础代码

上一篇入门中有与DeepSeek联网搜索对话的代码,这里放一个对话基础代码。

我本地是没有问题的(狗头)。有问题随时交流。

在DeepSeek官网购买获取API-KEY后替换到代码中。

python 复制代码
import os

# 基础对话所需
from langchain_deepseek import ChatDeepSeek
from langchain_core.messages import SystemMessage, HumanMessage


# 环境key
os.environ["DEEPSEEK_API_KEY"] = 'sk-xx'

def get_deepseek_key():
    key = os.getenv('DEEPSEEK_API_KEY')
    if key is None:
        raise ValueError("DEEPSEEK_API_KEY not found in environment variables.")
    return key

# --- LLM 初始化 ---
def create_deepseek_llm():
    api_key = get_deepseek_key()
    if not api_key:
        raise ValueError("没有ds key")
    return ChatDeepSeek(
        model = "deepseek-chat",
        temperature=0.1, # 低温度,更具备确定性
        max_tokens=1024, 
        timeout=None,
        max_retries=2,
        api_key=api_key
    )

def test_simple_llm_call():
    llm = create_deepseek_llm()
    # 封装消息
    # LangChain 支持的消息类型如下:
    # - 'human': 人类消息
    # - 'user': 用户消息
    # - 'ai': AI 消息
    # - 'assistant': 助手消息
    # - 'function': 函数消息
    # - 'tool': 工具消息
    # - 'system': 系统消息
    # - 'developer': 开发者消息
    messages = [
        ("system","你是一个乐于助人的LangChain专家。"),
        ("human","你好,请用一句话介绍LangChain。")
    ]
    
    # 流式响应
    for chunk in llm.stream(messages):
        print(chunk.text(), end="")
    print("\n AI回答完了")
    
    # 单词响应
    # response = llm.invoke(messages)
    # print("AI:", response.content)


if __name__ == '__main__':
    test_simple_llm_call()

LangChain python官方文档

输出可控性

结构化输入输出:提示模板Prompt Templates

LangChain提供了组件 Prompt Templates(提示模板)用来"让你的指令更规范、灵活,并且可以动态地根据用户输入或上下文调整"。

python 复制代码
# 导入提示模板
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate

场景:我们创建一个LangChain专家的提示模板

python 复制代码
import os

# 基础对话所需
from langchain_deepseek import ChatDeepSeek
from langchain_core.messages import SystemMessage, HumanMessage

# 提示模板
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate


# 环境key
os.environ["DEEPSEEK_API_KEY"] = 'sk-xx'

def get_deepseek_key():
    key = os.getenv('DEEPSEEK_API_KEY')
    if key is None:
        raise ValueError("DEEPSEEK_API_KEY not found in environment variables.")
    return key

# --- LLM 初始化 ---
def create_deepseek_llm():
    api_key = get_deepseek_key()
    if not api_key:
        raise ValueError("没有ds key")
    return ChatDeepSeek(
        model = "deepseek-chat",
        temperature=0.1, # 低温度,更具备确定性
        max_tokens=1024,
        timeout=None,
        max_retries=2,
        api_key=api_key
    )

 
# --- 使用模板对话限定输入输出 ---
def create_langchain_teacher_template():
    # 创建基础对话模板 
    """ 
    我们能在一些消息的构建上看到这种使用消息对象的写法。
    但是实际上这不是推荐的简洁写法,应该直接使用元组。
    
    是的,这个写法不会被视为一个s-string模板,而是一个string
    return ChatPromptTemplate.from_messages([
            SystemMessage(content="你是一个乐于助人的LangChain专家。"),
            HumanMessage(content="你好,我想问一个关于LangChain的问题: {actual_user_input}")
    ])
    """
    return ChatPromptTemplate.from_messages([
            ("system", "你是一个乐于助人的LangChain专家。"),
            ("human", "你好,我想问一个关于LangChain的问题: {actual_user_input}") # 确保这里是占位符
    ])

def test_simple_template_call():
    llm = create_deepseek_llm()
    user_question = "你好,请问LangChain组件Prompt Templates可以做什么?"
    prompt_template = create_langchain_teacher_template()
    messages = prompt_template.format_messages(actual_user_input=user_question)
    
    print("最终发送给 LLM 的消息:")
    for msg in messages:
        print(f"- 类型: {msg.type}, 内容: {msg.content}")
    
    
    print("\nAI开始回答")
    for chunk in llm.stream(messages):
        print(chunk.content, end="")
    print("\nAI回答完了")
    

if __name__ == '__main__':
    test_simple_template_call()

Prompt Templates可以告诉LLM它应该扮演什么角色、提供上下文、引导输出。

这里需要注意的是,在使用ChatPromptTemplate.from_messages()方法创建ChatPromptTemplate实例时,使用元组消息而是不是使用消息对象。 ("human", "...") 语法糖在幕后为你做了更多的工作,将字符串自动识别和处理为模板。而直接使用 HumanMessage(content="...") 时,content 被视为最终内容,而不是一个待格式化的模板。

应对不同场景的不同模板

LangChain提供了多种模板工具。

PromptTemplate

用于通用提示,适合单一角色的输入(如单一系统指令或用户输入)。 结构简单,适用于生成文本、回答问题、翻译等任务。

python 复制代码
template = """
你是{persona},一个{description},以{tone}的语气回应以下问题:
问题:{query}
回答:
"""
prompt = PromptTemplate(input_variables=["persona", "description", "tone", "query"], template=template)
formatted_prompt = prompt.format(persona="福尔摩斯", description="天才侦探", tone="机智", query="你能破这个案子吗?")
# 输出:以福尔摩斯机智风格的回答。
ChatPromptTemplates

代码中使用的模板就是这个,专为对话场景设计,支持多角色消息(如系统、用户、助手)。适合模拟聊天、角色扮演或多轮对话,结构为消息列表。

MessagePlaceholder

MessagePlaceholder 是 LangChain 中用于 ChatPromptTemplate 的特殊占位符,允许在对话模板中动态插入消息列表(如对话历史、用户输入或其他角色消息)。 适合需要灵活处理多轮对话或多角色交互的场景,例如聊天机器人、带上下文的问答。

前面的代码加入MessagePlaceholder后如下:

python 复制代码
import os
from langchain_deepseek import ChatDeepSeek

# 提示模板
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory

# 环境key
# 请确保你已经设置了你的 API Key
os.environ["DEEPSEEK_API_KEY"] = 'sk-xx'

def get_deepseek_key():
    key = os.getenv('DEEPSEEK_API_KEY')
    if key is None:
        raise ValueError(
            "DEEPSEEK_API_KEY 未在环境变量中找到。")
    return key

# --- LLM 初始化 ---


def create_deepseek_llm():
    api_key = get_deepseek_key()
    # get_deepseek_key 已经检查了 key 是否存在,这里无需重复检查
    return ChatDeepSeek(
        model="deepseek-chat",
        temperature=0.1,  # 低温度,更具确定性
        max_tokens=1024,
        timeout=None,
        max_retries=2,
        api_key=api_key
    )

# --- 创建带上下文的模板 ---


def create_langchain_teacher_template():
    return ChatPromptTemplate.from_messages([
        ("system", "你是一个乐于助人的LangChain专家,基于对话历史提供准确、简洁的回答。"),
        MessagesPlaceholder(variable_name="history"), # optional=True 也可以,但如果历史总是存在,则非必需
        ("human", "你好,我想问一个关于LangChain的问题: {actual_user_input}")
    ])

# --- 测试带上下文的对话 ---


def test_context_aware_template_call():
    llm = create_deepseek_llm()
    prompt_template = create_langchain_teacher_template()

    # 初始化记忆机制
    # 告诉记忆模块人类输入对应的键名是什么
    memory = ConversationBufferMemory(
        return_messages=True,
        memory_key="history",  # 这是默认值,与 MessagesPlaceholder 匹配
        input_key="actual_user_input"  # 关键:告诉记忆模块当前人类输入对应的键名
    )

    # 告诉 ConversationChain 它的主要输入键是什么
    chain = ConversationChain(
        llm=llm,
        prompt=prompt_template,
        memory=memory,
        input_key="actual_user_input",  # 关键:告诉链,run()方法中的哪个参数是主要的用户输入
        verbose=True # 开启详细模式,方便调试,会打印完整的提示
    )

    # 第一次提问
    user_question_1 = "你好,请问LangChain组件Prompt Templates可以做什么?"
    print(f"\n用户提问: {user_question_1}")
    # 当调用 run 时,使用在 ConversationChain 的 input_key 中指定的键
    response_1 = chain.run(actual_user_input=user_question_1)
    print("AI回答:", response_1)

    # 第二次提问,依赖上下文
    user_question_2 = "那它支持多轮对话吗?"
    print(f"\n用户提问: {user_question_2}")
    response_2 = chain.run(actual_user_input=user_question_2)
    print("AI回答:", response_2)

    # 你可以检查记忆中的内容
    print("\n--- 记忆内容 ---")
    print(memory.load_memory_variables({}))


if __name__ == '__main__':
    # 请确保你设置了 API Key
    if os.getenv('DEEPSEEK_API_KEY') == 'sk-6d65661014664acd8267025af3bfe925' or not os.getenv('DEEPSEEK_API_KEY'):
        print("警告: DEEPSEEK_API_KEY 正在使用占位符或未设置。请输入你真实的 API Key。")
        # 如果没有设置,可以临时在这里设置一个(仅为示例结构,请替换为你的真实Key)
        if not os.getenv('DEEPSEEK_API_KEY'):
             os.environ["DEEPSEEK_API_KEY"] = 'sk-6d65661014664acd8267025af3bfe925' # 临时使用占位符
             print("临时使用占位符API Key进行演示。请务必替换它!")

    test_context_aware_template_call()

输出解析器 Output Parsers(输出解析器)

现在,我们已经了解了基本对话和使用提示词模板Prompt Templates来限制输出。有时候我们需要结构化的输出数据,比如JSON、列表等... LangChain提供了Output Parsers(输出解析器)来解析大模型返回文本。

以Prompt Templates的代码为基础,我们导入类库,询问LangChain的3个优点

python 复制代码
import os

# 基础对话所需
from langchain_deepseek import ChatDeepSeek
from langchain_core.messages import SystemMessage, HumanMessage

# 提示模板
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate

# 输出解析器
from langchain.output_parsers import CommaSeparatedListOutputParser

# 链条
from langchain.chains import LLMChain, SimpleSequentialChain, SequentialChain

# 环境key
os.environ["DEEPSEEK_API_KEY"] = 'sk-xx'

def get_deepseek_key():
    key = os.getenv('DEEPSEEK_API_KEY')
    if key is None:
        raise ValueError("DEEPSEEK_API_KEY not found in environment variables.")
    return key

# --- LLM 初始化 ---
def create_deepseek_llm():
    api_key = get_deepseek_key()
    if not api_key:
        raise ValueError("没有ds key")
    return ChatDeepSeek(
        model = "deepseek-chat",
        temperature=0.1, # 低温度,更具备确定性
        max_tokens=1024,
        timeout=None,
        max_retries=2,
        api_key=api_key
    )

 
# --- 使用模板对话限定输入输出 ---
def create_langchain_teacher_template():
    return ChatPromptTemplate.from_messages([
            ("system", "你是一个乐于助人的LangChain专家。"),
            ("human", "你好,我想问一个关于LangChain的问题: {actual_user_input} \n\n {format_instructions}") # 确保这里是占位符
    ])


# --- 使用输出解析器解析输出 ---
def create_output_parser():
    return CommaSeparatedListOutputParser()

def test_output_parser_with_template():
    llm = create_deepseek_llm()
    output_parser = create_output_parser()
    """
    1.获取格式化指令
    对于 CommaSeparatedListOutputParser,它会是类似 "Your response should be a list of comma separated values, eg: `foo, bar, baz`"
    """
    format_instructions = output_parser.get_format_instructions()
    print(f"输出解析器的格式化指令: {format_instructions}")
    
    prompt_template = create_langchain_teacher_template()
    user_task = "请列出 LangChain 的3个主要优点。"
    
    # 2.格式化完整提示,包含用户任务和格式指令
    messages_for_llm = prompt_template.format_messages(
        actual_user_input=user_task,
        format_instructions=format_instructions
    )
    
    print("\n最终发送给 LLM 的消息 (包含格式指令):")
    for msg in messages_for_llm:
        print(f"- 类型: {msg.type}, 内容: {msg.content}")
    
    # 3. 调用LLM并获取原始文本输出
    print("\nAI开始生成原始文本 (等待 LLM 响应)...")
    ai_response_message = llm.invoke(messages_for_llm)
    raw_llm_output = ai_response_message.content # AIMessage对象的content属性是字符串
    print(f"LLM 返回的原始文本: '{raw_llm_output}'")
    
    # 4. 解析原始文本输出
    try:
        parsed_output = output_parser.parse(raw_llm_output)
        print("\n解析后的输出 (Python列表):")
        print(parsed_output)
        if isinstance(parsed_output, list):
            print("LangChain 的3个优点是:")
            for i,advantage in enumerate(parsed_output):
                print(f"{i}. {advantage.strip()}")
    except Exception as e:
        print(f"解析输出时出错: {e}")
        print("这通常意味着 LLM 的输出没有严格遵循格式化指令。")
        print("你可以尝试调整提示,或者使用更鲁棒的解析器/重试机制。")
    
    print("--- 结束测试:带输出解析器的模板调用 ---\n")



if __name__ == '__main__':
    test_output_parser_with_template()
相关推荐
MrGaoGang16 小时前
AI应用开发:LangGraph+MCP
前端·人工智能·langchain
西部荒野子19 小时前
LangChain.js 中的 Runnable 系统
langchain
大尾巴青年1 天前
06 一分钟搞懂langchain的Agent是如何工作的
langchain·llm
敲键盘的小夜猫2 天前
LangChain核心之Runnable接口底层实现
langchain
疯狂的小强呀2 天前
基于langchain的简单RAG的实现
python·langchain·rag检索增强
用户711283928472 天前
LangChain(三) LCEL
人工智能·langchain
啾啾大学习2 天前
LangChain快速筑基(带代码)P3-连续对话Memory
langchain
啾啾大学习2 天前
LangChain快速筑基(带代码)P0-DeepSeek对话与联网搜索
langchain
啾啾大学习2 天前
LangChain快速筑基(带代码)P7-LLM外部数据检索Retrievers
langchain