🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

前端開發者的 AI 學習筆記 🚀

image

這是一篇關於個人學習 AI 的筆記與程式碼摘錄。希望從前端的視角出發,快速了解大型語言模型(LLM)、提示詞工程、LangChain、RAG 等相關術語知識,最終能夠搭建一個 “玩具智能體” 或者真正應用到生產中去。


目錄 📑

  • LLM 基礎:深入 AI 的心臟
  • 提示詞工程:與 AI 對話的藝術
  • LangChain.js 實戰:搭建你的第一個 AI 應用
  • RAG:讓 AI 擁有你的專屬知識庫
  • 多模態:讓 AI 看見聽見
  • 其他術語

LLM 基礎:深入 AI 的心臟 🤖

要理解 AI 應用,我們首先要抓住大型語言模型(LLM)的核心。它的本質其實非常樸素:一次一個 token 地補全內容,或者說,不斷地“預測下一個詞”。

比如下面,會計算接下來字符的出現概率

image

透過不斷預測下個詞,最終生成一段話:

image

但這個預測過程並非總是選擇概率最高的詞,否則每次的回答都會一成不變。為了引入創造性,模型在採樣時會加入一些隨機性。這可以透過 temperature (溫度) 這個參數來控制:

  • 較低的 temperature (如 0.2): 回答更具確定性和穩定性,適合需要事實性回答的場景。
  • 較高的 temperature (如 0.8): 回答更具發散性和創意。

那麼,模型是如何理解我們輸入的文字的呢?計算機會將文字(字串)轉換為數字(向量)。這個過程通常分為兩步:第一步 One-Hot 編碼,第二步,將編碼結果進行壓縮。

如下圖,文本中的每個 Token 都會對應一個向量。起初,這是一個包含大量 0 的稀疏向量。

image

經過 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 哪個大這種,符合這個場景)

image

  • ReAct 框架:即 Reasoning(推理)+ Acting(行動)。它將大模型的推理能力和調用外部工具(如搜索引擎)的行動能力結合起來,讓 AI 能夠解決自身知識庫之外的問題。

LangChain.js 實戰:搭建你的第一個 AI 應用 🧩

LangChain 是一個強大的開源框架,它能幫助我們輕鬆地構建和組合各種大模型應用,我們稱之為“鏈”(Chain)。

它的生態還包括:

  • LangServe: 用於快速部署。
  • LangSmith: 用於調試和監控。
  • LangGraph: 用於構建複雜 Agent。

其核心是 LCEL(LangChain 表達式語言),一種用管道符 | 將不同組件聲明式地組裝在一起的表達式語言,非常清晰且易於重用。

【【---下面程式碼摘自 python 學習資料,不需要完全理解具體實現,能知道有那麼個 API 能夠在各階段進行相應的處理就行了。---】】

ChatModel:基礎的對話調用

從最基礎的開始:如何與一個聊天模型進行互動。下面的程式碼展示了如何發送系統和用戶消息,並獲得模型的回覆。

from langchain_core.messages import HumanMessage, SystemMessage
from langchain_openai import ChatOpenAI
import os

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 來優雅地解決這個問題。

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 可以幫助我們定義輸出格式,並自動解析模型的返回結果。

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 提供了多種方式來管理會話歷史,讓我們的應用能夠進行連續的多輪對話。

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),即檢索增強生成

image

它的核心思想很簡單,就是“先查後答”。具體流程如下:

  1. 接收問題: 用戶提出問題。
  2. 檢索: 不直接把問題丟給大模型,而是先用它去檢索我們自己的知識庫(比如公司的產品文檔、個人筆記等)。
  3. 增強: 把檢索到的相關內容和原始問題一起打包,作為更豐富的上下文(Context)交給大模型。
  4. 生成: 讓大模型基於這些信息來生成最終答案。
用戶問題
   ↓
文本轉 Embedding → 檢索知識庫(向量匹配)
   ↓
  找到相關內容
   ↓
 大模型生成答案(融合外部信息)

關於向量數據庫的見解
向量數據庫的核心,是將文本等多模態數據轉化為高維空間中的向量。每一個詞、每一段話,都相應地成為 N 維空間中的一個點。
這種表示方式的強大之處在於,我們可以透過計算這些點之間的“距離”或“夾角”來量化它們的語義相似度。以二維空間為例,兩個向量的點積可以揭示它們的關係:

  • 銳角 (點積為正): 表示語義相似。
  • 垂直 (點積為零): 表示語義無關。
  • 鈍角 (點積為負): 表示語義背離或相反。
    這種從幾何角度理解語義的算法,不僅是 RAG 的基石,也是許多推薦系統的核心原理,讓機器能夠在海量信息中找到“鄰近”的內容。

image

數據入庫:構建你的向量知識庫

首先,我們需要將文檔加載、切分、並轉換為向量,存入專門的向量數據庫中。

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 的閉環

當用戶提問時,我們從向量數據庫中檢索最相似的文檔片段,並將其提供給模型。

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):
// ...
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):
// ...
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):
// 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):
// 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):
// ...
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 的世界這麼大,後面會邊學邊補充,覺得有幫助不妨點個收藏夾~~


原文出處:https://juejin.cn/post/7555692185020170249


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝19   💬9   ❤️5
723
🥈
我愛JS
📝4   💬13   ❤️7
243
🥉
AppleLily
📝1   💬3   ❤️1
69
#4
御魂
💬1  
3
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付