前端开发者的 AI 学习笔记 🚀

这是一篇关于个人学习 AI 的笔记与代码摘录。希望从前端的视角出发,快速了解大语言模型(LLM)、提示词工程、LangChain、RAG 等相关术语知识,最终能够搭建一个 "玩具智能体" 或者真正应用到生产中去。


目录 📑

  • LLM 基础:深入 AI 的心脏
  • 提示词工程:与 AI 对话的艺术
  • LangChain.js 实战:搭建你的第一个 AI 应用
  • RAG:让 AI 拥有你的专属知识库
  • 多模态:让 AI 看见听见
  • 其他术语

LLM 基础:深入 AI 的心脏 🤖

要理解 AI 应用,我们首先要抓住大语言模型(LLM)的核心。它的本质其实非常朴素:一次一个 token 地补全内容,或者说,不断地"预测下一个词"。

比如下面,会计算接下来字符的出现概率

通过不断预测下个词,最终生成一段话:

但这个预测过程并非总是选择概率最高的词,否则每次的回答都会一成不变。为了引入创造性,模型在采样时会加入一些随机性。这可以通过 temperature (温度) 这个参数来控制:

  • 较低的 temperature (如 0.2): 回答更具确定性和稳定性,适合需要事实性回答的场景。
  • 较高的 temperature (如 0.8): 回答更具发散性和创意。

那么,模型是如何理解我们输入的文字的呢?计算机会将文字(字符串)转换为数字(向量)。这个过程通常分为两步:第一步 One-Hot编码,第二步,将编码结果进行压缩。

如下图,文本中的每个 Token 都会对应一个向量。起初,这是一个包含大量 0 的稀疏向量。

经过 Embedding 处理后,一个硕大的向量就会压缩成一个固定大小的向量。比如在 GPT3 中,每个 Token 由768个数字组成的向量表示。

在与大模型 API(以 OpenAI 为例)交互时,有一些常用参数可以帮助我们精确地控制模型的行为。这里我们对它们进行一个简单的记录和解释,便于理解和查询(不同的大模型 API 参数会有差异)。

核心参数

这些是每次调用时几乎都会用到的基础参数。

  • model: (字符串) 指定要使用的模型 ID,例如 "gpt-4o-mini"。
  • messages: (数组) 包含了整个对话历史的消息列表。每个消息都是一个对象,包含 role (角色,如 "system", "user", "assistant") 和 content (内容)。这是模型理解上下文的关键。
  • temperature: (数字, 0-2) 控制输出的随机性。
  • max_tokens: (整数) 设置在生成的回复中允许的最大 token 数量。这是一个控制回复长度和 API 成本的有效手段。
  • stream: (布尔值) 如果设置为 true,API 会以数据流(Server-Sent Events)的形式分块返回结果,可以实现前端的"打字机"效果。如果为 false,则会等所有内容生成完毕后一次性返回。

工程参数

  • user: (字符串) 最终用户的唯一标识符。
  • n: (整数) 为每条输入消息生成的回复数量。
  • response_format: (对象) 规定模型输出的格式,如 JSON。

工具类参数

用于扩展模型的能力,让它能与外部世界交互。

  • tools: (数组) 模型可调用的外部工具(函数)列表。
  • tool_choice: (字符串或对象) 控制模型如何选择和调用工具。

采样与行为参数

用于更精细地调整模型的生成策略。

  • seed: (整数) 用于实现可复现、确定性输出的采样种子。
  • stop: (字符串或数组) 模型生成时遇到即停止的文本序列。
  • frequency_penalty: (数字, -2.0 到 2.0) 降低模型重复已生成文本的惩罚值。
  • logit_bias: (对象) 手动调整特定 Token 的出现概率。
  • top_p: (数字, 0-1) 一种替代 temperature 的核采样方法,控制生成文本的多样性。

提示词工程:与 AI 对话的艺术 🧠

与大模型沟通的艺术,就是提示词工程(Prompt Engineering)。一个好的提示词能极大提升模型的表现。

从用户的角度来看,一个结构化的提示词可以遵循这样一个公式:

提示词 = 定义角色 + 背景信息 + 任务目标 + 输出要求

在实践中,有几种常见的提示词范式可以帮助我们更好地引导模型:

  • 零样本(Zero-shot):直接向模型提出你的需求,适用于那些模型已经很熟悉的通用任务。
  • 少样本(Few-shot):在提问前给模型一两个示例或参照,这在处理特定或复杂任务时尤其有效。
  • 思维链(Chain-of-Thought, CoT):引导模型"慢下来",一步一步地思考,并把推理过程展示出来。这不仅能提高复杂问题的准确率,还能让我们了解它的"思考"轨迹。 下图左边是让大模型直接返回结果,右边是提示大模型慢思考。(比如 1.11 和 1.2 哪个大这种,符合这个场景)
  • ReAct 框架 :即 R easoning(推理)+ Acting(行动)。它将大模型的推理能力和调用外部工具(如搜索引擎)的行动能力结合起来,让 AI 能够解决自身知识库之外的问题。

LangChain.js 实战:搭建你的第一个 AI 应用 🧩

LangChain 是一个强大的开源框架,它能帮助我们轻松地构建和组合各种大模型应用,我们称之为"链"(Chain)。

它的生态还包括:

  • LangServe: 用于快速部署。
  • LangSmith: 用于调试和监控。
  • LangGraph: 用于构建复杂 Agent。

其核心是 LCEL(LangChain Expression Language),一种用管道符 | 将不同组件声明式地组装在一起的表达式语言,非常清晰且易于复用。

【【---下面代码摘自 python 学习资料,不需要完全理解具体实现,能知道有那么个 API 能够在各阶段进行相应的处理就行了。---】】

ChatModel:基础的对话调用

从最基础的开始:如何与一个聊天模型进行交互。下面的代码展示了如何发送系统和用户消息,并获得模型的回复。

python 复制代码
from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI

os.environ["OPENAI_API_KEY"] = "你的 API Key"
os.environ["OPENAI_API_BASE"] = "你的 API Base"

messages = [
    SystemMessage(content="Translate the following from English into Chinese:"),
    HumanMessage(content="Welcome to LLM application development!"),
]

model = ChatOpenAI(model="gpt-4o-mini")
result = model.invoke(messages)
print(result)

PromptTemplate:优雅管理提示词

为了让代码更清晰、更易于维护,我们通常不会把给开发者的指令和用户的输入混在一起。LangChain 提供了 PromptTemplate 来优雅地解决这个问题。

python 复制代码
from langchain_core.prompts import ChatPromptTemplate
from langchain_openai import ChatOpenAI

prompt_template = ChatPromptTemplate.from_messages(
    [
        ("system", "Translate the following from English into Chinese:"),
        ("user", "{text}")
    ]
)

model = ChatOpenAI(model="gpt-4o-mini")

chain = prompt_template | model
result = chain.invoke({"text":"Welcome to LLM application development!"})
print(result)

OutputParser:获取结构化的输出

有时我们希望模型返回的是严格的 JSON 格式或其他结构化数据,而不是纯文本。OutputParser 可以帮助我们定义输出格式,并自动解析模型的返回结果。

python 复制代码
from langchain_core.output_parsers import JsonOutputParser
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

class Work(BaseModel):
    title: str = Field(description="Title of the work")
    description: str = Field(description="Description of the work")

parser = JsonOutputParser(pydantic_object=Work)

prompt = PromptTemplate(
    template="列举3部{author}的作品。\n{format_instructions}",
    input_variables=["author"],
    partial_variables={"format_instructions": parser.get_format_instructions()},
)

model = ChatOpenAI(model="gpt-4o-mini")

chain = prompt | model | parser
result = chain.invoke({"author": "老舍"})
print(result)

会话记忆:让机器人"记住"上下文

标准的 API 调用是无状态的,但聊天机器人需要记住之前的对话。LangChain 提供了多种方式来管理会话历史,让我们的应用能够进行连续的多轮对话。

python 复制代码
from langchain_core.chat_history import BaseChatMessageHistory, InMemoryChatMessageHistory
from langchain_core.runnables import RunnableWithMessageHistory
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage

chat_model = ChatOpenAI(model="gpt-4o-mini")

store = {}

def get_session_history(session_id: str) -> BaseChatMessageHistory:
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

with_message_history = RunnableWithMessageHistory(chat_model, get_session_history)

config = {"configurable": {"session_id": "dreamhead"}}

while True:
    user_input = input("You:> ")
    if user_input.lower() == 'exit':
        break
    stream = with_message_history.stream(
        [HumanMessage(content=user_input)],
        config=config
    )
    for chunk in stream:
        print(chunk.content, end='', flush=True)
    print()

RAG:让 AI 拥有你的专属知识库 🔍

大模型虽然知识渊博,但它的知识是静态的(截止到某个训练日期),而且不包含你的私有数据。

那么,如何让模型"知道更多"呢?答案就是 RAG(Retrieval-Augmented Generation),即检索增强生成

它的核心思想很简单,就是"先查后答"。具体流程如下:

  1. 接收问题: 用户提出问题。

  2. 检索: 不直接把问题丢给大模型,而是先用它去检索我们自己的知识库(比如公司的产品文档、个人笔记等)。

  3. 增强: 把检索到的相关内容和原始问题一起打包,作为更丰富的上下文(Context)交给大模型。

  4. 生成: 让大模型基于这些信息来生成最终答案。

    用户问题

    文本转 Embedding → 检索知识库(向量匹配)

    找到相关内容

    大模型生成答案(融合外部信息)

关于向量数据库的见解

向量数据库的核心,是将文本等多模态数据转化为高维空间中的向量。每一个词、每一段话,都相应地成为 N 维空间中的一个点。

这种表示方式的强大之处在于,我们可以通过计算这些点之间的"距离"或"夹角"来量化它们的语义相似度。以二维空间为例,两个向量的点积可以揭示它们的关系:

  • 锐角 (点积为正): 表示语义相似。
  • 垂直 (点积为零): 表示语义无关。
  • 钝角 (点积为负): 表示语义背离或相反。

这种从几何角度理解语义的算法,不仅是 RAG 的基石,也是许多推荐系统的核心原理,让机器能够在海量信息中找到"邻近"的内容。

数据入库:构建你的向量知识库

首先,我们需要将文档加载、切分、并转换为向量,存入专门的向量数据库中。

python 复制代码
from langchain_community.document_loaders import TextLoader

loader = TextLoader("introduction.txt")
docs = loader.load()

text_splitter = RecursiveCharacterTextSplitter(chunk_size=1000, chunk_overlap=200)
splits = text_splitter.split_documents(docs)
vectorstore = Chroma(
    collection_name="ai_learning",
    embedding_function=OpenAIEmbeddings(),
    persist_directory="vectordb"
)
vectorstore.add_documents(splits)

检索与生成:完成 RAG 的闭环

当用户提问时,我们从向量数据库中检索最相似的文档片段,并将其提供给模型。

python 复制代码
vectorstore = Chroma(
    collection_name="ai_learning",
    embedding_function=OpenAIEmbeddings(),
    persist_directory="vectordb"
)

retriever = vectorstore.as_retriever(search_type="similarity")
# Retriever 承担 RAG 中的 R:根据文本查询文档(Document)

关键点:RAG 的成功与否,很大程度上取决于数据处理的质量。包括:

  • 如何有效地提取数据源
  • 选择合适的分块(Chunking)策略
  • 优化向量化和检索算法等

多模态:让 AI 看见听见 🏞️🗣️

现代的 AI 应用早已超越了纯文本的范畴。

多模态意味着模型能够理解和处理多种类型的信息,如文本、图片、音频和视频。作为前端开发者,我们需要掌握如何在客户端处理这些复杂的输入和输出。

下面,我们将通过几个具体的场景,来看看前端是如何与多模态模型进行交互的。

场景一:文本与流式文本 (Text & Streaming)

这是最基础的交互。除了"一次性"返回所有结果,更优的用户体验是"流式"返回,即像打字机一样逐字显示内容。

在前端,这通常通过 fetch API 结合 ReadableStream 来实现。

  • 关键代码 (Chat.vue):

    typescript 复制代码
    // ...
    if(stream.value) {
      const completion = await openai.chat.completions.create({
        model: MODEL,
        stream: true, // 关键参数
        messages: [ /* ... */ ]
      });
      // 逐块读取流
      for await (const chunk of completion as any) {
        const delta = chunk?.choices?.[0]?.delta?.content;
        if(delta) content.value += delta;
      }
    } else {
      const completion = await openai.chat.completions.create({ /* ... */ });
      content.value = completion.choices?.[0]?.message?.content || '(无返回)';
    }
    // ...
  • 解释 :当 stream: true 时,返回的是一个数据流。我们通过 for await...of 循环来异步地迭代这个流,每次迭代得到一个数据块(chunk),然后将增量内容(delta)追加到界面上,实现了流畅的打字机效果。

场景二:文生图 (Text-to-Image)

调用文生图模型时,前端需要构造一个包含详细参数的请求,并将返回的图片 URL 展示出来。

  • 关键代码 (image.vue):

    typescript 复制代码
    // ...
    const body = {
      model: 'qwen-image',
      input: {
        messages: [
          { role: 'user', content: [ { text: prompt.value.trim() } ] }
        ]
      },
      parameters: {
        negative_prompt: negativePrompt.value || '',
        size: size.value
      }
    };
    const resp = await fetch('/api/.../multimodal-generation/generation', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${apiKey}` },
      body: JSON.stringify(body)
    });
    const json = await resp.json();
    // 从复杂的 JSON 结构中解析出图片 URL
    const choices = json?.output?.choices || [];
    for (const c of choices) {
      const contents = c?.message?.content || [];
      if (Array.isArray(contents)) {
        for (const item of contents) {
          if (item?.image && typeof item.image === 'string') list.push(item.image);
        }
      }
    }
    // ...
  • 解释 :前端将用户的提示词、反向提示词和期望尺寸等参数打包成一个 JSON 对象发送给模型。请求成功后,需要根据 API 的约定,从层层嵌套的 JSON 响应中解析出最终的图片 URL 数组,并将其渲染到 <img> 标签上。

场景三:流式文本转语音 (Streaming TTS)

为了实现低延迟的语音合成,前端可以接收实时的音频流并立即播放,而不是等待整个音频文件生成完毕。这通常使用 SSE (Server-Sent Events) 协议。

  • 关键代码 (TTS.vue):

    typescript 复制代码
    // ...
    // 1. 发起 SSE 请求
    const resp = await fetch('/api/.../multimodal-generation/generation', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'X-DashScope-SSE': 'enable' // 启用 SSE
      },
      body: JSON.stringify(body),
    });
    
    // 2. 实时处理音频流
    const reader = resp.body.getReader();
    const decoder = new TextDecoder();
    while(true) {
      const { done, value } = await reader.read();
      if(done) break;
      // ... 解析 SSE 消息 ...
      const chunkB64 = out.audio_chunk; // 获取 Base64 编码的 PCM 音频块
      if(!chunkB64) continue;
      
      const pcm = base64ToBytes(chunkB64);
      playPcmRealtime(pcm, sampleRate); // 实时播放
    }
    
    // 3. 使用 Web Audio API 播放 PCM 数据
    let audioCtx = new AudioContext();
    let scheduledTime = 0;
    function playPcmRealtime(pcm, sr) {
      if(!audioCtx) return;
      const frameCount = pcm.length / 2; // 16-bit
      const abuf = audioCtx.createBuffer(1, frameCount, sr);
      // ... 将 PCM 数据写入 AudioBuffer ...
      const src = audioCtx.createBufferSource();
      src.buffer = abuf;
      src.connect(audioCtx.destination);
      src.start(scheduledTime); // 精确调度播放时间,避免爆音
      scheduledTime += abuf.duration;
    }
    // ...
  • 解释

    1. 请求头中加入 'X-DashScope-SSE': 'enable' 来告诉服务端我们需要一个 SSE 连接。
    2. 使用 ReadableStreamTextDecoder 来逐行读取和解析服务端推送的事件。
    3. 每个事件中包含一小段 Base64 编码的原始音频数据(PCM)。
    4. 我们使用 Web Audio API (AudioContext) 将这些 PCM 数据解码成 AudioBuffer,并通过 createBufferSource 创建一个音频源进行无缝播放,实现了几乎无延迟的语音合成效果。

场景四:图生视频 (Image-to-Video)

视频生成通常是耗时很长的异步任务。前端在提交请求后不会立刻得到结果,而是会收到一个任务 ID。之后,前端需要通过这个 ID 定期去轮询(Poll)任务状态,直到任务完成并获取视频 URL。

  • 关键代码 (Video.vue):

    typescript 复制代码
    // 1. 提交异步任务
    const resp = await fetch('/api/.../video-synthesis', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${apiKey}`,
        'X-DashScope-Async': 'enable' // 启用异步模式
      },
      body: JSON.stringify(body)
    });
    const json = await resp.json();
    const returnedTaskId = json?.output?.task_id;
    if (returnedTaskId) {
      pollTask(returnedTaskId); // 开始轮询
    }
    
    // 2. 轮询任务状态
    const pollTask = async (id, interval = 5000) => {
      const endpoint = `/api/v1/tasks/${id}`;
      for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        const r = await fetch(endpoint, { headers: { 'Authorization': `Bearer ${apiKey}` } });
        const j = await r.json();
        const s = j?.output?.task_status;
        
        const v = findVideoUrl(j); // 尝试从返回中寻找视频 URL
        if (v) {
          videoUrl.value = v; // 找到了!停止轮询
          return;
        }
        if (s === 'SUCCEEDED') { /* 任务成功但可能 URL 在别处,继续解析 */ }
        
        await new Promise(res => setTimeout(res, interval)); // 等待 5 秒再查
      }
    };
  • 解释

    1. 请求头中加入 'X-DashScope-Async': 'enable' 来启动一个异步任务。
    2. 从初始响应中获取 task_id
    3. 启动一个 pollTask 函数,该函数会每隔几秒钟(例如 5 秒)调用任务查询接口,检查任务状态。
    4. 当任务状态变为 SUCCEEDED 或直接在响应中找到视频 URL 时,轮询结束,前端将视频展示给用户。

场景五:图片文字识别 (OCR)

对于 OCR 这样的视觉语言模型,API 调用方式也发生了变化。我们需要在一个请求中同时包含图片和文本指令。

  • 关键代码 (Ocr.vue):

    typescript 复制代码
    // ...
    const body = {
      model: 'qwen-vl-ocr',
      messages: [
        { role: 'user', content: [
          { type: 'image_url', image_url: imageUrl.value.trim() },
          { type: 'text', text: buildPrompt() } // "请识别图片中全部文字..."
        ]}
      ]
    };
    const resp = await fetch('.../chat/completions', { /* ... */ });
    // ...
  • 解释 :在 messages 数组中,content 不再是一个简单的字符串,而是一个包含不同类型对象的数组。

    • { type: 'image_url', ... } 用来指定图片地址。
    • { type: 'text', ... } 用来给出具体的指令(比如要求返回纯文本还是 JSON)。 这种灵活的结构让我们可以轻松地实现复杂的图文混合输入。

其他术语 🛠️

在构建更复杂的 AI 应用时,我们会接触到一些关键的工具和协议:

  • MCP(Model Context Protocol):Agent 与外部工具(Tool)之间沟通的协议,统一了上下文与调用规则。
  • Function Calling:让模型能够调用外部函数或 API 的机制。
  • History:在会话类应用中用于保存和管理上下文的机制。
  • 向量数据库与索引:专门用于存储和高效检索向量数据的数据库,是 RAG 的核心。
  • Schema:数据结构的规范,用于约束输入、输出或工具的格式。

写在最后 ✍️

AI 的世界这么大,后面会边学边补充,觉得有帮助不妨点个收藏夹~~

相关推荐
高热度网2 小时前
从 Vercel 构建失败谈 Git 大小写敏感性问题:一个容易被忽视的跨平台陷阱
前端·javascript
青衫旧故2 小时前
Uniapp Vue2 Vue3常量保存及调用
前端·javascript·vue.js·uni-app
知白守黑2673 小时前
访问控制、用户认证、https
linux·服务器·前端
小妖怪的夏天3 小时前
electron 打包web页面解决跨域问题
前端·javascript·electron
骚饼3 小时前
Git 命令配置别名、Git命令缩写(Mac版)
前端·git
LoveEate3 小时前
vue3 el-switch表单联动校验
前端·javascript·vue.js
z_y_j2299704383 小时前
服务器中更新前端项目
服务器·前端
2301_797604243 小时前
d40: vue杂项问题
前端·javascript·vue.js
Mintopia4 小时前
领域适配 AIGC:垂直行业 Web 应用的微调技术实践
前端·javascript·aigc