LLM的生态与能力边界&一个基本对话的实现

学习目标

概念

  • 理解大模型应用的整体价值与边界
  • 熟悉大模型常见应用形态及其典型优势与不足。
  • 掌握用"问题建模"思维来判断 AI 的适用性。

1.1 大模型应用生态概览

  • 模型层:包括商用大模型(如 GPT、Claude)、开源大模型(如 LLaMA、Mistral)以及专用领域模型。
  • 数据层:语料来源、知识库构建、标注与清洗方法。
  • 工具与基础设施层:提示管理、上下文管理、RAG 框架、向量数据库、可观测性工具。
  • 评测与运维层:指标体系、红队测试、安全合规、版本管理。
  • 部署与产品层:原型验证、业务集成、用户体验优化。

1.2 大模型典型应用形态

  • 文本生成:文章撰写、文案生成、代码补全。
  • 信息检索与问答:基于知识库的智能问答、企业 FAQ 自动化。
  • 任务自动化:通过函数调用或 Agent 完成 API 调用、工作流编排。
  • 多模态理解:文本+图像/语音/视频的综合应用。
  • 辅助决策:基于大模型的总结与洞察,辅助人类判断。

1.3 能力与局限

  • 优势
    • 通用性强,可快速适配不同任务。
    • 具备语言理解与生成能力,覆盖广泛领域。
    • 可结合外部知识库与工具提升能力。
  • 局限
    • 幻觉问题:模型会生成看似合理但错误的信息。
    • 不可控性:输出难以完全预测。
    • 隐私与安全风险:存在敏感数据泄露与越狱风险。
    • 成本与延迟:大模型推理资源消耗高,实时性受限。

1.4 问题建模思维

  • 场景分析:是否真的需要大模型,还是传统算法更优?
  • 需求匹配:问题是生成型(写/创作)还是判别型(分类/检测)?
  • 约束条件:数据安全、延迟、成本、合规性。
  • 可控性设计:是否需要引入检索增强(RAG)、规则约束或人工审核。

实现一个基本的LLM对话功能

这里不一定用LangChain啊,只是我习惯了,并且想再熟悉下LangChain的开发,很多人不喜欢用LangChain,因为他太重了,得了解很多包和方法什么的,不如模型供应商自己的sdk,确实是这样的,不过我们这里主要是去理解LLM的过程,使用什么不重要,以后你去实现的时候可以选择自己喜欢的SDK或者框架

早期我的前公司开发的时候就没有选择用框架用了模型厂商提供的sdk,对应的function call、agent调用就是自己实现的,但在LangChain封装了很多的工具调用,其实更方便一些

LangChain

当然可以!让我给你详细介绍一下 LangChain,并把它的核心概念和用途讲清楚。


1. 什么是 LangChain?

LangChain 是一个用于构建 语言模型驱动应用(LLM Applications) 的框架,它帮助开发者把大语言模型(如 GPT、Claude 等)和各种数据源、工具、外部 API 结合起来,快速搭建复杂的应用。

简单来说:

如果把 GPT 只当作"问答工具",LangChain 就是帮你把它变成"可执行任务的智能助理"的框架。

2. 核心功能

a. 链(Chains)

链是 LangChain 的核心概念之一。

  • 它把 多个步骤(例如输入处理 → 模型推理 → 输出处理)串联起来。
  • 可以是简单链(单步处理)或复杂链(多步决策流程)。 示例:
rust 复制代码
用户输入 -> 文本摘要 -> 翻译 -> 返回结果
b. 提示模板(Prompt Templates)
  • 让你以可复用、可动态填充的方式构建提示(prompt)。
  • 支持变量替换,方便自动化任务。

示例:

arduino 复制代码
import { PromptTemplate } from "langchain/prompts";

// 定义提示模板
const template = "请把以下文本翻译成{language}: {text}";
const prompt = new PromptTemplate({
  template,                  // 模板字符串
  inputVariables: ["text", "language"],  // 变量列表
});

// 使用示例
(async () => {
  const result = await prompt.format({ text: "你好,世界", language: "英文" });
  console.log(result); // 输出: 请把以下文本翻译成英文: 你好,世界
})();
c. 工具(Tools)
  • LangChain 可以让 LLM 调用外部工具或 API。
  • 例如:
    • 检索知识库(Documents / Vector Database)
    • 执行计算
    • 访问网络或数据库
  • 这样 LLM 不只是"回答问题",还能执行任务
d. 记忆(Memory)
  • 支持长期或会话记忆,让 LLM 能"记住"用户信息或上下文。
  • 可用于构建对话系统、智能助手等。
e. 代理(Agents)
  • 代理是结合 LLM + 工具 + 决策逻辑的"智能行动体"。
  • 它能:
    • 自动选择要用的工具
    • 决定任务执行顺序
    • 根据结果调整下一步操作

3. 应用场景

  1. 智能问答系统
    • 结合向量数据库,让 LLM 回答公司文档、产品手册的问题。
  2. 自动化办公助手
    • 自动处理邮件、生成报告、整理日程。
  3. 多步骤任务执行
    • 例如:根据客户请求生成代码 → 测试 → 部署。
  4. 对话型 AI
    • 带上下文记忆、能调用外部 API 的聊天机器人。

4. 技术栈 / 生态

  • Python / TypeScript SDK
  • 支持各种 LLM(OpenAI GPT、Anthropic Claude、Local LLM 等)
  • 集成常用数据库、向量搜索、API 调用工具
  • 开发者社区活跃,有很多开箱即用的示例

总结一下:

LangChain 是一个构建复杂语言模型应用的开发框架,核心在于"链 + 工具 + 记忆 + 代理",让 LLM 不只是聊天,而是真正能执行任务、处理多步骤工作的智能系统。

使用nextjs + LangChain实现一个基本问答

实现效果

使用LangChain调用三方转发的API

1.配置 ChatOpenAI 实例

LangChain支持调用三方转发的api,进行模型调用,需要配置,baseURL和openAIApiKey即可

js 复制代码
const chatInstance = new ChatOpenAI({
  openAIApiKey: process.env.OPEN_API_KEY,
  modelName,
  temperature,
  maxTokens,
  configuration: { baseURL: process.env.OPEN_API_BASE_URL },
  verbose: false,
});

关键参数

  • openAIApiKey:调用 OpenAI API 的密钥。
  • modelName:如 gpt-3.5-turbo
  • temperature / maxTokens:生成文本控制。
  • configuration.baseURL:可选,自定义 API 基础地址。
  • verbose:调试模式,输出更多日志信息。

LLM交互的三种信息类型

1. 三种消息类型(信息类型)

在 LangChain 的对话设计中,其他的模型厂商的SDK也是这样的大差不差,每条消息都会有一个 角色 ,对应 三种类型的调用

js 复制代码
const langchainMessages = [];
if (systemMessage) langchainMessages.push(new SystemMessage(systemMessage));

for (const message of messages) {
  switch (message.role) {
    case 'user':
      langchainMessages.push(new HumanMessage(message.content));
      break;
    case 'assistant':
      langchainMessages.push(new AIMessage(message.content));
      break;
    case 'system':
      break; // 系统消息已添加
  }
}
类型 对应类 作用
System(系统) SystemMessage 提供模型行为指令,比如"你是一个有用的 AI 助手",用于控制生成风格、角色和约束。
Human(用户) HumanMessage 用户输入内容,是模型要回答的主要信息。
AI(助手) AIMessage 历史助手消息,用于保留上下文,让模型能参考前几轮对话生成连贯回答。

这就是你在代码里看到的三类消息:

js 复制代码
new SystemMessage(systemMessage)
new HumanMessage(message.content)
new AIMessage(message.content)

它们是 LangChain 消息数组的核心组成部分,用于构建对话上下文。

2. 三种调用模式(接口层面)

在调用模型时,LangChain 也有三种主要模式:

类型 方法 特点
非流式调用 invoke(messages) 一次性生成完整回答,返回整个文本。适合简单场景,但用户要等模型生成完成才能看到内容。
流式调用 stream(messages) 返回一个异步迭代器,模型生成时逐步输出内容。前端可以边生成边显示,实现 SSE 流式渲染。
高级链式调用 / 工具调用 Chains / Agents 通过 LangChain 的链或代理接口,把 LLM 与检索器、API、数据库等结合,形成更复杂调用逻辑(例如问答+知识库+API执行)。

当前主要用了前两种

3. 区分这三种的原因
  • System / Human / AI 消息类型:保证上下文完整,模型知道"谁在说什么",控制生成风格。
  • 非流式 / 流式 / 链式调用模式 :保证调用方式灵活,能适应不同的用户体验需求:
    • 非流式:简单快速实现。
    • 流式:提升用户体验,减少等待。
    • 链式/工具调用:扩展应用场景,例如知识库问答或自动化任务执行。

简单总结:

  • 三种消息类型 → 信息层面(System / Human / AI)
  • 三种调用模式 → 接口/调用层面(非流式 / 流式 / 链式)

两者结合,LangChain 可以灵活地构建对话应用:既能让模型理解上下文,又能控制输出方式。

流式输出实现之SSE

SSE协议是单向的,服务端往客户端推送数据,在浏览器中是可以预览的

1. ReadableStream

ReadableStreamWeb Streams API 的一部分,用于表示一个 可读的数据流。它可以从网络请求、文件、或者自定义源中按块读取数据,而不是一次性加载全部内容。

核心概念
  • 流(Stream) :数据不是一次性返回,而是分块(chunk)传输。
  • 控制器(Controller) :负责把数据块推送到流里。
  • 读者(Reader) :消费流里的数据块。
创建方式

首先在后端,我们用它来把 AI 生成的内容逐步发送给前端:

js 复制代码
const readable = new ReadableStream({
  async start(controller) {
    for await (const chunk of aiStream) {
      controller.enqueue(new TextEncoder().encode(chunk));
    }
    controller.close();
  }
});
  • start(controller):初始化流,在这里把数据逐块推送到前端。
  • controller.enqueue():把一块数据加入流。
  • controller.close():关闭流,告诉前端数据发送完毕。

2. getReader()

getReader()ReadableStream 的方法,用于获取 流的读取器,从而逐块消费数据。

作用
  • 将可读流转换成 异步迭代器 ,前端可以用 while(true) 循环逐块读取数据。
  • 配合 AbortController 可以中途停止读取。
示例
js 复制代码
const reader = response.body?.getReader();

while (true) {
  const { done, value } = await reader.read();
  if (done) break; // 流结束
  console.log('收到一块数据:', value);
}
  • reader.read() 返回一个对象 { done, value }
    • done: boolean 表示流是否结束
    • value: Uint8Array 是本次读取的数据块
  • 可以在循环中实时处理每块数据,实现 流式渲染
3. TextDecoder

TextDecoderWeb API ,用于将 字节流(Uint8Array) 转换成 字符串

在 SSE 或 ReadableStream 中,后端发送的数据是 二进制块,前端需要解码成文本。

示例
js 复制代码
const decoder = new TextDecoder();
const text = decoder.decode(value); // 将 Uint8Array 转为字符串
console.log(text);
  • 可以支持不同编码,如 'utf-8'(默认)、'iso-8859-1' 等。
  • 对于流式数据,可以多次调用 decode(),逐块累加生成完整文本。

4. 三者在流式问答中的协作
  1. 后端生成流:
js 复制代码
const readable = new ReadableStream({
  start(controller) {
    for await (const chunk of aiStream) {
      controller.enqueue(new TextEncoder().encode(chunk));
    }
    controller.close();
  }
});
  1. 前端获取流:
js 复制代码
const reader = response.body.getReader();
const decoder = new TextDecoder();
let assistantMessage = '';
  1. 逐块读取并更新界面
js 复制代码
while (true) {
  const { done, value } = await reader.read();
  if (done) break;
  const textChunk = decoder.decode(value);
  assistantMessage += textChunk;
  setMessages(prev => [...prev.slice(0, -1), { ...assistantMessage }]);
}

这样就实现了 边生成边显示的效果,而无需等待 AI 完整生成。

完整代码

后端

js 复制代码
import { NextRequest, NextResponse } from 'next/server';
import { ChatOpenAI } from '@langchain/openai';
import {
  HumanMessage,
  SystemMessage,
  AIMessage,
} from '@langchain/core/messages';

export interface ChatMessage {
  role: 'user' | 'assistant' | 'system';
  content: string;
}

export interface ChatRequest {
  messages: ChatMessage[];
  temperature?: number;
  maxTokens?: number;
  modelName?: string;
  systemMessage?: string;
  stream?: boolean;
}

export async function POST(request: NextRequest) {
  try {
    const body: ChatRequest = await request.json();
    const {
      messages,
      temperature = 0.7,
      maxTokens = 2000,
      modelName = 'gpt-3.5-turbo',
      systemMessage = '你是一个有用的AI助手,请用简体中文回答用户问题。',
      stream = true,
    } = body;

    // 验证输入
    if (!messages || messages.length === 0) {
      return NextResponse.json({ error: '消息不能为空' }, { status: 400 });
    }

    // 构建 LangChain 消息数组
    const langchainMessages = [];

    // 添加系统消息
    if (systemMessage) {
      langchainMessages.push(new SystemMessage(systemMessage));
    }

    // 转换消息格式
    for (const message of messages) {
      switch (message.role) {
        case 'user':
          langchainMessages.push(new HumanMessage(message.content));
          break;
        case 'assistant':
          langchainMessages.push(new AIMessage(message.content));
          break;
        case 'system':
          // 系统消息已经在前面添加了
          break;
        default:
          console.warn('Unknown message role:', message.role);
      }
    }

    // 配置模型
    const chatInstance = new ChatOpenAI({
      openAIApiKey: process.env.OPEN_API_KEY,
      modelName,
      temperature,
      maxTokens,
      configuration: {
        baseURL: process.env.OPEN_API_BASE_URL,
      },
      verbose: false,
    });

    if (stream) {
      // 流式响应
      const stream = await chatInstance.stream(langchainMessages);

      const encoder = new TextEncoder();
      const readable = new ReadableStream({
        async start(controller) {
          try {
            for await (const chunk of stream) {
              const data = `data: ${JSON.stringify({
                content: chunk.content,
                done: false,
              })}\n\n`;
              controller.enqueue(encoder.encode(data));
            }
            const doneData = `data: ${JSON.stringify({
              content: '',
              done: true,
            })}\n\n`;
            controller.enqueue(encoder.encode(doneData));
            controller.close();
          } catch (error) {
            console.error('Stream error:', error);
            const errorData = `data: ${JSON.stringify({
              error: '生成响应时发生错误',
              details: error instanceof Error ? error.message : '未知错误',
              done: true,
            })}\n\n`;
            controller.enqueue(encoder.encode(errorData));
            controller.close();
          }
        },
      });

      return new Response(readable, {
        headers: {
          'Content-Type': 'text/stream',
          'Cache-Control': 'no-cache',
          Connection: 'keep-alive',
          'Access-Control-Allow-Origin': '*',
          'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
          'Access-Control-Allow-Headers': 'Content-Type, Authorization',
        },
      });
    } else {
      // 非流式响应
      const response = await chatInstance.invoke(langchainMessages);
      return NextResponse.json({
        message: {
          role: 'assistant',
          content: response.content,
        },
        usage: response.usage_metadata,
        model: modelName,
        timestamp: new Date().toISOString(),
      });
    }
  } catch (error) {
    console.error('Chat API Error:', error);

    // 检查是否是API密钥相关错误
    const errorMessage = error instanceof Error ? error.message : '未知错误';
    let userFriendlyMessage = '处理请求时发生错误';

    if (errorMessage.includes('API key')) {
      userFriendlyMessage = 'API密钥未配置或无效,请检查环境变量 OPEN_API_KEY';
    } else if (errorMessage.includes('rate limit')) {
      userFriendlyMessage = 'API调用频率限制,请稍后再试';
    } else if (
      errorMessage.includes('network') ||
      errorMessage.includes('fetch')
    ) {
      userFriendlyMessage = '网络连接错误,请检查网络或 OPEN_API_BASE_URL 配置';
    }

    return NextResponse.json(
      {
        error: userFriendlyMessage,
        details:
          process.env.NODE_ENV === 'development' ? errorMessage : undefined,
        timestamp: new Date().toISOString(),
      },
      { status: 500 }
    );
  }
}

前端

ini 复制代码
'use client';

import { useState, useRef, useEffect } from 'react';
import TestPageLayout from '@/components/TestPageLayout';
import { Send, Loader2, AlertCircle } from 'lucide-react';

interface ChatMessage {
  role: 'user' | 'assistant';
  content: string;
}

export default function ChatPage() {
  const [messages, setMessages] = useState<ChatMessage[]>([]);
  const [input, setInput] = useState('');
  const [isLoading, setIsLoading] = useState(false);
  const [error, setError] = useState<string | null>(null);
  const messagesEndRef = useRef<HTMLDivElement>(null);
  const abortControllerRef = useRef<AbortController | null>(null);

  // 自动滚动到底部
  const scrollToBottom = () => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' });
  };

  useEffect(() => {
    scrollToBottom();
  }, [messages]);

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    if (!input.trim() || isLoading) return;

    const userMessage: ChatMessage = { role: 'user', content: input.trim() };
    setMessages((prev) => [...prev, userMessage]);
    setInput('');
    setIsLoading(true);
    setError(null);

    // 创建新的 AbortController
    abortControllerRef.current = new AbortController();

    try {
      const response = await fetch('/api/chat', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({
          messages: [...messages, userMessage],
          stream: true,
        }),
        signal: abortControllerRef.current.signal,
      });

      if (!response.ok) {
        const errorData = await response.json();
        throw new Error(errorData.error || `HTTP ${response.status}`);
      }

      // 添加空的助手消息
      let assistantMessage: ChatMessage = { role: 'assistant', content: '' };
      setMessages((prev) => [...prev, assistantMessage]);

      // 处理流式响应
      const reader = response.body?.getReader();
      const decoder = new TextDecoder();

      if (!reader) {
        throw new Error('无法读取响应流');
      }

      while (true) {
        const { done, value } = await reader.read();

        if (done) {
          break;
        }

        const chunk = decoder.decode(value);
        const lines = chunk.split('\n');

        for (const line of lines) {
          if (line.startsWith('data: ')) {
            try {
              const data = JSON.parse(line.slice(6));

              if (data.error) {
                throw new Error(data.error);
              }

              if (data.content && !data.done) {
                assistantMessage.content += data.content;
                setMessages((prev) => {
                  const newMessages = [...prev];
                  newMessages[newMessages.length - 1] = { ...assistantMessage };
                  return newMessages;
                });
              }

              if (data.done) {
                break;
              }
            } catch (parseError) {
              // 忽略解析错误,继续处理下一行
            }
          }
        }
      }
    } catch (error) {
      console.error('Chat error:', error);

      if (error instanceof Error) {
        if (error.name === 'AbortError') {
          // 用户取消了请求
          return;
        }
        setError(error.message);
      } else {
        setError('发生未知错误');
      }

      // 移除未完成的助手消息
      setMessages((prev) => {
        const newMessages = [...prev];
        if (
          newMessages.length > 0 &&
          newMessages[newMessages.length - 1].role === 'assistant' &&
          newMessages[newMessages.length - 1].content === ''
        ) {
          newMessages.pop();
        }
        return newMessages;
      });
    } finally {
      setIsLoading(false);
      abortControllerRef.current = null;
    }
  };

  const handleStop = () => {
    if (abortControllerRef.current) {
      abortControllerRef.current.abort();
      setIsLoading(false);
    }
  };

  const clearChat = () => {
    setMessages([]);
    setError(null);
  };

  return (
    <TestPageLayout
      title="简单对话"
      description="测试基础的LLM对话功能,支持上下文记忆和流式响应"
    >
      <div className="flex flex-col h-[600px]">
        {/* 错误提示 */}
        {error && (
          <div className="mx-6 mt-4 p-4 bg-red-50 border border-red-200 rounded-lg flex items-center space-x-2">
            <AlertCircle className="h-5 w-5 text-red-600" />
            <div className="flex-1">
              <p className="text-red-800 font-medium">发生错误</p>
              <p className="text-red-600 text-sm">{error}</p>
            </div>
            <button
              onClick={() => setError(null)}
              className="text-red-600 hover:text-red-800"
            >
              ✕
            </button>
          </div>
        )}

        {/* 消息列表 */}
        <div className="flex-1 overflow-y-auto p-6 space-y-4">
          {messages.length === 0 ? (
            <div className="text-center text-gray-500 mt-20">
              <p className="text-lg mb-2">开始你的第一次对话</p>
              <p className="text-sm">在下方输入框中输入消息,按回车发送</p>
              <p className="text-xs text-gray-400 mt-2">
                支持流式响应和上下文记忆
              </p>
            </div>
          ) : (
            <>
              {messages.map((message, index) => (
                <div
                  key={index}
                  className={`flex ${message.role === 'user' ? 'justify-end' : 'justify-start'}`}
                >
                  <div
                    className={`max-w-[70%] p-3 rounded-lg ${
                      message.role === 'user'
                        ? 'bg-primary text-primary-foreground'
                        : 'bg-gray-100 text-gray-900'
                    }`}
                  >
                    <p className="whitespace-pre-wrap break-words">
                      {message.content}
                    </p>
                  </div>
                </div>
              ))}

              {/* 加载动画 */}
              {isLoading && (
                <div className="flex justify-start">
                  <div className="bg-gray-100 p-3 rounded-lg flex items-center space-x-2">
                    <Loader2 className="h-4 w-4 animate-spin" />
                    <span className="text-gray-600">AI正在思考...</span>
                  </div>
                </div>
              )}
            </>
          )}
          <div ref={messagesEndRef} />
        </div>

        {/* 工具栏 */}
        {messages.length > 0 && (
          <div className="px-6 py-2 border-t bg-gray-50">
            <div className="flex justify-between items-center text-sm text-gray-600">
              <span>消息数: {messages.length}</span>
              <button
                onClick={clearChat}
                className="text-red-600 hover:text-red-800 hover:underline"
                disabled={isLoading}
              >
                清空对话
              </button>
            </div>
          </div>
        )}

        {/* 输入区域 */}
        <div className="border-t p-6">
          <form onSubmit={handleSubmit} className="flex space-x-4">
            <input
              type="text"
              value={input}
              onChange={(e) => setInput(e.target.value)}
              placeholder="输入你的消息..."
              className="flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-primary focus:border-transparent"
              disabled={isLoading}
            />
            {isLoading ? (
              <button
                type="button"
                onClick={handleStop}
                className="px-6 py-2 bg-red-600 text-white rounded-lg hover:bg-red-700 flex items-center space-x-2"
              >
                <span>停止</span>
              </button>
            ) : (
              <button
                type="submit"
                disabled={!input.trim()}
                className="px-6 py-2 bg-primary text-primary-foreground rounded-lg hover:bg-primary/90 disabled:opacity-50 disabled:cursor-not-allowed flex items-center space-x-2"
              >
                <Send className="h-4 w-4" />
                <span>发送</span>
              </button>
            )}
          </form>
          <div className="mt-2 text-xs text-gray-500 text-center">
            支持上下文记忆 • 流式响应 • 按 Enter 发送
          </div>
        </div>
      </div>
    </TestPageLayout>
  );
}
相关推荐
LFly_ice3 小时前
学习React-16-useContext
前端·学习·react.js
陈陈CHENCHEN3 小时前
使用 Vite 快速创建 React 项目 - SuperMap iClient JavaScript / Leaflet
前端·react.js
用户6883362059703 小时前
Progressive Web App (PWA)
前端
沢田纲吉3 小时前
《LLVM IR 学习手记(二):变量表达式编译器的实现与深入解析》
前端·编程语言·llvm
小徐_23333 小时前
VitePress 博客变身 APP,支持离线访问,只需这一招。
前端·vitepress·pwa
猪哥帅过吴彦祖3 小时前
第 5 篇:WebGL 从 2D 到 3D - 坐标系、透视与相机
前端·javascript·webgl
折七3 小时前
expo sdk53+ 集成极光推送消息推送 ios swift
前端·javascript·ios
猪哥帅过吴彦祖3 小时前
Flutter 系列教程:布局基础 (上) - `Container`, `Row`, `Column`, `Flex`
前端·flutter·ios
lifejump3 小时前
DVWA | XSS 跨站脚本注入
前端·xss