【LLM LangChain】 进行简单聊天的完整demo示例 + 获取apikey & 调用

简单聊天

// 代码地址 https://github.com/langchain-ai/langchain-nextjs-template/blob/main/app/api/chat/route.ts
import { NextRequest, NextResponse } from "next/server";// 导入 Request/Response 类型 ,React是单页应用,Next有req和res功能,https://blog.youkuaiyun.com/ResumeProject/article/details/151250286?
import { Message as VercelChatMessage, StreamingTextResponse } from "ai"; // 从 Vercel 的 AI SDK 导入消息类型和流式响应工具

// 从 LangChain 导入 OpenAI模型、Prompt 模板、输出解析器
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { HttpResponseOutputParser } from "langchain/output_parsers";

export const runtime = "edge"; // 指定运行环境为 Edge Runtime(低延迟,适合流式输出)

const formatMessage = (message: VercelChatMessage) => { return `${message.role}: ${message.content}`;      }; // 消息格式化工具函数:把消息对象转成 "role: 内容" 格式的字符串
// 定义 Prompt 模板 —— 机器人扮演一个啰嗦的海盗 🏴‍☠️
const TEMPLATE = `You are a pirate named Patchy. All responses must be extremely verbose and in pirate dialect. 
Current conversation: \n {chat_history} \n User: {input} \n AI:`;

// POST 接口:处理聊天请求,解析前端传来的消息,流式返回结果给前端
export async function POST(req: NextRequest) {
  try {
    // 从请求体中取出 messages
    const body = await req.json();
    const messages = body.messages ?? [];
   
    const formattedPreviousMessages = messages.slice(0, -1).map(formatMessage); // 历史消息(除最后一条外) => 格式化成字符串
    const currentMessageContent = messages[messages.length - 1].content; // 当前用户输入 => 取最后一条消息的内容
    
    // LangChain组合拳
    const prompt = PromptTemplate.fromTemplate(TEMPLATE);// 用模板生成 Prompt 实例
    const model = new ChatOpenAI({ // 初始化 ChatOpenAI 模型,temperature=0.8 表示回答更有创造性,使用 gpt-4o-mini 作为底层模型
      temperature: 0.8, model: "gpt-4o-mini",
    });
    const outputParser = new HttpResponseOutputParser(); // 定义输出解析器 Chat返回的消息流=》浏览器能消费的 HTTP 流
    const chain = prompt.pipe(model).pipe(outputParser);// 组装链(Pipeline) : Prompt -> ChatOpenAI -> OutputParser
    const stream = await chain.stream({ // 执行链,传入历史记录和当前输入填充模板
      chat_history: formattedPreviousMessages.join("\n"),
      input: currentMessageContent,
    });

    // vercel ai库的返回流式响应,前端可逐块接收内容。搭配HttpResponseOutputParser()。  类似langchain中的for await (const chunk of stream) {process.stdout.write(chunk);}
    return new StreamingTextResponse(stream);
  } catch (e: any) {
    // 错误处理:返回 JSON 格式错误信息
    return NextResponse.json({ error: e.message }, { status: e.status ?? 500 });
  }
}

安装依赖

npm install langchain
npm install @langchain/openai @langchain/core
npm install ai@3.4.33  # https://www.npmjs.com/package/ai

代码结构

  • 官方初始化命令:

    npx create-next-app@latest my-app
    
  • 代码结构
    在这里插入图片描述

  • Next.js 默认的 API 路由规范是 src/app/api/chat/route.tssrc/pages/api/chat.ts。这样前端请求 /api/chat 能自动匹配到你的接口。如果你放在其他路径,需要确保该文件被正确导出为 API 路由。

添加界面相关代码

"use client";# 声明这是一个 客户端组件(在浏览器中运行,而不是只在服务器渲染)
import Image from "next/image";
import { useState } from "react";

export default function Home() {
  const [messages, setMessages] = useState([
    { role: "system", content: "Ahoy! Patchy the Pirate be at yer service. What be yer question?" }
  ]);
  const [input, setInput] = useState("");
  const [loading, setLoading] = useState(false);


  # sendMessage函数
  const sendMessage = async () => {
    if (!input.trim()) return;
    setLoading(true);
    const newMessages = [...messages, { role: "user", content: input }];// [...messages, ...] 使用扩展运算符 (Spread Operator) 将现有消息数组 messages 中的所有元素复制到新数组中
    setMessages(newMessages);

    const res = await fetch("/api/chat", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ messages: newMessages }),
    });

    if (res.ok) {
      const reader = res.body?.getReader(); # 以 流式读取(streaming) 的方式接收服务器返回的数据(常见于 AI 模型的逐字生成)
      let aiReply = "";
      if (reader) {
        const decoder = new TextDecoder(); # 把二进制数据转成字符串
        while (true) {
          const { done, value } = await reader.read();
          if (done) break;
          aiReply += decoder.decode(value);
          setMessages(msgs => [ # 每次读到新的数据,就更新最后一条 assistant 的回复,实现“边生成边显示”,用state实现https://blog.youkuaiyun.com/ResumeProject/article/details/143633857
            ...msgs.slice(0, -1),
            msgs[msgs.length - 1],
            { role: "assistant", content: aiReply }
          ]);
        }
      }
    } else {
      setMessages(msgs => [
        ...msgs,
        { role: "assistant", content: "Arrr! There be an error with yer request." }
      ]);
    }
    setInput("");
    setLoading(false);
  };

  # UI 部分
  return (
    <div className="font-sans grid grid-rows-[20px_1fr_20px] items-center justify-items-center min-h-screen p-8 pb-20 gap-16 sm:p-20">
      <main className="flex flex-col gap-[32px] row-start-2 items-center sm:items-start">
        <Image
          className="dark:invert"
          src="/next.svg"
          alt="Next.js logo"
          width={180}
          height={38}
          priority
        />
        {/* Chat UI */}
        <div className="w-full max-w-md bg-white dark:bg-gray-900 rounded-lg shadow p-4 flex flex-col gap-4">
          <div className="flex flex-col gap-2 h-64 overflow-y-auto border-b pb-2">
            {messages.map((msg, idx) => (
              <div key={idx} className={`text-sm ${msg.role === "user" ? "text-blue-700" : "text-green-700"}`}>
                <b>{msg.role === "user" ? "You" : "Patchy"}:</b> {msg.content}
              </div>
            ))}
          </div>
          <div className="flex gap-2">
            <input
              className="flex-1 border rounded px-2 py-1"
              value={input}
              onChange={e => setInput(e.target.value)}
              onKeyDown={e => e.key === "Enter" && sendMessage()}
              disabled={loading}
              placeholder="Type your message..."
            />
            <button
              className="bg-blue-600 text-white px-4 py-1 rounded"
              onClick={sendMessage}
              disabled={loading}
            >
              Send
            </button>
          </div>
        </div>
        {/* ...existing code... */}
      </main>
      {/* ...existing code... */}
    </div>
  );
}

显示结果:

在这里插入图片描述

获取apikey & 调用

OPENAI APIKEY

在这里插入图片描述
在这里插入图片描述

QWen APIKEY

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 实名认证

在这里插入图片描述

调用QWEN

    const model = new ChatOpenAI({
      // 此处以qwen-plus为例,您可按需更换模型名称。模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
      model: "qwen-plus",
      apiKey: "sk-0cff***********373f1",
      configuration: {
        baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
        // other params...
      },
      // other params...
    });

DEEPSEEK APIKEY

在这里插入图片描述

CG

实际使用示例

import { NextRequest, NextResponse } from "next/server";// 导入 Request/Response 类型 ,React是单页应用,Next有req和res功能,https://blog.youkuaiyun.com/ResumeProject/article/details/151250286?
import { Message as VercelChatMessage, StreamingTextResponse } from "ai"; // 从 Vercel 的 AI SDK 导入消息类型和流式响应工具

// 从 LangChain 导入 OpenAI模型、Prompt 模板、输出解析器
import { ChatOpenAI } from "@langchain/openai";
import { PromptTemplate } from "@langchain/core/prompts";
import { HttpResponseOutputParser } from "langchain/output_parsers";

export const runtime = "edge"; // 指定运行环境为 Edge Runtime(低延迟,适合流式输出)

const formatMessage = (message: VercelChatMessage) => { return `${message.role}: ${message.content}`;      }; // 消息格式化工具函数:把消息对象转成 "role: 内容" 格式的字符串
// 定义 Prompt 模板 —— 机器人扮演一个啰嗦的海盗 🏴‍☠️
// const TEMPLATE = `You are a pirate named Patchy. All responses must be extremely verbose and in pirate dialect. 
// Current conversation: \n {chat_history} \n User: {input} \n AI:`;

// POST 接口:处理聊天请求,解析前端传来的消息,流式返回结果给前端
export async function POST(req: NextRequest) {
  try {
    // 从请求体中取出 messages
    const body = await req.json();
    const messages = body.messages ?? [];
   
    const formattedPreviousMessages = messages.slice(0, -1).map(formatMessage); // 历史消息(除最后一条外) => 格式化成字符串
    const currentMessageContent = messages[messages.length - 1].content; // 当前用户输入 => 取最后一条消息的内容
    
    // LangChain组合拳
    const prompt = PromptTemplate.fromTemplate(process.env.ONE_TEMPLATE);// 用模板生成 Prompt 实例
    // const model = new ChatOpenAI({ // 初始化 ChatOpenAI 模型,temperature=0.8 表示回答更有创造性,使用 gpt-4o-mini 作为底层模型
    //   temperature: 0.8, model: "gpt-4o-mini",openAIApiKey: "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",
    // });
    const model = new ChatOpenAI({
      // 此处以qwen-plus为例,您可按需更换模型名称。模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
      model: "qwen-plus",
      apiKey: process.env.MODEL_API_KEY, 
      configuration: {
        baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
        // other params...
      },
      // other params...
    });

    const outputParser = new HttpResponseOutputParser(); // 定义输出解析器 Chat返回的消息流=》浏览器能消费的 HTTP 流
    const chain = prompt.pipe(model).pipe(outputParser);// 组装链(Pipeline) : Prompt -> ChatOpenAI -> OutputParser
    const stream = await chain.stream({ // 执行链,传入历史记录和当前输入填充模板
      chat_history: formattedPreviousMessages.join("\n"),
      input: currentMessageContent,
    });

    // vercel ai库的返回流式响应,前端可逐块接收内容。搭配HttpResponseOutputParser()。  类似langchain中的for await (const chunk of stream) {process.stdout.write(chunk);}
    return new StreamingTextResponse(stream);
  } catch (e: any) {
    // 错误处理:返回 JSON 格式错误信息
    return NextResponse.json({ error: e.message }, { status: e.status ?? 500 });
  }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值