学习目标
概念
- 理解大模型应用的整体价值与边界
- 熟悉大模型常见应用形态及其典型优势与不足。
- 掌握用"问题建模"思维来判断 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. 应用场景
- 智能问答系统
- 结合向量数据库,让 LLM 回答公司文档、产品手册的问题。
- 自动化办公助手
- 自动处理邮件、生成报告、整理日程。
- 多步骤任务执行
- 例如:根据客户请求生成代码 → 测试 → 部署。
- 对话型 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
ReadableStream
是 Web 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
TextDecoder
是 Web 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. 三者在流式问答中的协作
- 后端生成流:
js
const readable = new ReadableStream({
start(controller) {
for await (const chunk of aiStream) {
controller.enqueue(new TextEncoder().encode(chunk));
}
controller.close();
}
});
- 前端获取流:
js
const reader = response.body.getReader();
const decoder = new TextDecoder();
let assistantMessage = '';
- 逐块读取并更新界面:
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>
);
}