Chuyển đến nội dung chính

第6課:LLM推論管線設計

LLM推論參數:temperature、top-k、top-p。 NVIDIA NIM微服務部署模型。 LangChain LCEL管線。 Gradio與LangServe:建構UI + API。 對話管理與多輪對話。

1. 從Diffusion Models到LLM應用

在第2部分中,我們掌握了Diffusion Models——從前向/反向過程到CLIP引導生成。現在在第3部分,重點轉向大型語言模型(LLMs)以及如何建構實際應用:推論管線、RAG和聊天機器人。

本課聚焦於LLM推論管線設計——如何透過取樣參數控制LLM輸出、使用NVIDIA NIM部署模型、使用LangChain LCEL建構管線,以及使用Gradio + LangServe建立UI/API。

考試提示: NVIDIA DLI考試經常考推論參數(temperature、top-k、top-p)以及何時使用NIM而非其他框架。請務必熟記本課末尾的比較表。

LLM推論管線 — Prompt Template、NIM、LCEL Chain、Gradio UI
LLM推論管線 — Prompt Template、NIM、LCEL Chain、Gradio UI

2. LLM推論基礎

2.1. 自迴歸生成

LLM使用自迴歸機制生成文字:每一步模型根據所有先前的token預測下一個token。這個過程不斷重複,直到遇到停止token或達到max_tokens為止。


自迴歸生成流程
═══════════════════════════

輸入: "Hanoi is"
         │
         ▼
┌─────────────────────┐
│   LLM前向傳播         │
│   P(token | context) │
└──────────┬──────────┘
           │
           ▼
┌─────────────────────┐
│   取樣策略            │──► temperature, top-k, top-p
│   選擇下一個token     │
└──────────┬──────────┘
           │
           ▼
    token = "the"
           │
           ▼
輸入: "Hanoi is the"
         │
         ▼
┌─────────────────────┐
│   LLM前向傳播         │
└──────────┬──────────┘
           │
           ▼
    token = "capital"
           │
           ▼
   ... 重複直到 <EOS> 或 max_tokens

2.2. 取樣參數

控制輸出創造性的三個最重要參數:

參數範圍效果低值高值
temperature0.0 – 2.0調整機率分佈的熵確定性、重複創造性、更隨機
top_k1 – vocab_size僅限最高機率的前K個token更精確、多樣性較低更多選擇
top_p0.0 – 1.0核取樣:僅考慮累積機率≤p的token僅最確定的token考慮更多token

Token取樣過程(temperature + top-p)
═════════════════════════════════════════════

原始logits:  [2.1, 1.8, 0.5, 0.3, -1.0, -2.5, ...]
                │
                ▼
         ┌──────────────┐
         │  ÷ temperature │  (temp=0.7 → 更尖銳)
         └──────┬───────┘
                │
                ▼
縮放機率: [0.35, 0.28, 0.12, 0.09, 0.08, 0.05, 0.03]
                │
                ▼
         ┌──────────────┐
         │   top-p=0.8   │  cumsum: 0.35→0.63→0.75→0.84 ✓
         │   保留前4個    │  → 丟棄token 5,6,7...
         └──────┬───────┘
                │
                ▼
過濾後:   [0.41, 0.33, 0.14, 0.12]  (重新正規化)
                │
                ▼
         隨機取樣 → token "the"

2.3. 其他參數

參數說明使用場景
max_tokens限制輸出token的最大數量控制成本、延遲
stop遇到此字串時停止生成結構化輸出、函數呼叫
repetition_penalty懲罰已出現的token(>1.0 = 懲罰更重)避免詞彙/句子重複
frequency_penalty根據出現頻率降低機率更多樣化的輸出
presence_penalty若token已出現至少一次則懲罰鼓勵新主題

考試提示: 常見題目:「要始終獲得相同的輸出(確定性),應該設定哪個參數?」→ temperature = 0.0。若問「減少詞彙重複」→ 使用 repetition_penalty > 1.0 或 frequency_penalty > 0。

3. NVIDIA NIM(NVIDIA Inference Microservices)

3.1. 什麼是NIM?

NVIDIA NIM是一組預先最佳化的推論容器,能在NVIDIA GPU上以最高效能部署LLM/多模態模型。NIM內建TensorRT-LLM、量化和記憶體最佳化功能。

主要特點:

  • OpenAI相容API — 直接替換,可直接使用openai客戶端呼叫
  • TensorRT-LLM後端 — 針對NVIDIA GPU最佳化的核心運算
  • 持續批次處理 — 高效同時處理多個請求
  • gRPC + REST API — 靈活整合
  • 多GPU支援 — 自動張量平行化

3.2. NIM架構


NVIDIA NIM架構
════════════════════════

┌─────────────────────────────────────────────┐
│              NIM容器                          │
│                                              │
│  ┌──────────┐   ┌──────────────────────┐    │
│  │  REST API │   │   gRPC端點            │    │
│  │ :8000     │   │   :8001              │    │
│  └─────┬────┘   └──────────┬───────────┘    │
│        │                    │                │
│        └────────┬───────────┘                │
│                 ▼                             │
│  ┌──────────────────────────────────┐       │
│  │     請求路由 & 批次處理器           │       │
│  │     (持續批次處理)                  │       │
│  └──────────────┬───────────────────┘       │
│                 ▼                             │
│  ┌──────────────────────────────────┐       │
│  │     TensorRT-LLM引擎              │       │
│  │  ┌────────┐ ┌────────────────┐   │       │
│  │  │KV快取   │ │ 分頁注意力機制  │   │       │
│  │  └────────┘ └────────────────┘   │       │
│  └──────────────┬───────────────────┘       │
│                 ▼                             │
│  ┌──────────────────────────────────┐       │
│  │       NVIDIA GPU                  │       │
│  │   A100 / H100 / L40S            │       │
│  └──────────────────────────────────┘       │
└─────────────────────────────────────────────┘

3.3. 拉取與執行NIM容器


# Pull and run NIM container for Llama-3
# Requirements: NVIDIA GPU, Docker + NVIDIA Container Toolkit

# Terminal command:
# docker run -it --rm --gpus all \
#   -p 8000:8000 \
#   -e NGC_API_KEY=$NGC_API_KEY \
#   nvcr.io/nim/meta/llama-3.1-8b-instruct:latest

3.4. 呼叫NIM API


from openai import OpenAI

# NIM與OpenAI API相容——只需更改base_url
client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="not-used"  # 本機NIM不需要金鑰
)

response = client.chat.completions.create(
    model="meta/llama-3.1-8b-instruct",
    messages=[
        {"role": "system", "content": "You are a helpful AI assistant."},
        {"role": "user", "content": "Explain the Transformer architecture"}
    ],
    temperature=0.7,
    top_p=0.9,
    max_tokens=512
)

print(response.choices[0].message.content)

3.5. NIM與原生HuggingFace推論比較

標準NVIDIA NIMHuggingFace Transformers
後端TensorRT-LLMPyTorch
吞吐量(tokens/s)~2500-4000~300-800
延遲(TTFT)~50-100ms~200-500ms
批次處理持續批次處理手動 / 靜態
APIOpenAI相容RESTPython API
設置1個docker run命令安裝函式庫 + 撰寫程式碼
量化內建(FP8、INT4)需要額外的GPTQ/AWQ
生產就緒是(監控、擴展)需要額外的服務層

考試提示: 當考試問「在NVIDIA GPU上部署LLM最快的方式」或「具備TensorRT-LLM最佳化的生產就緒推論」時,NIM永遠是正確答案。NIM ≠ 訓練框架——它僅用於推論。

4. LangChain LCEL管線設計

4.1. 什麼是LCEL?

LangChain Expression Language(LCEL)是一種宣告式語法,用於建構LLM處理管線。它使用|(管道)運算子將元件串接在一起——類似於Unix管道。

LCEL的優勢:

  • 串流 — 支援逐token輸出串流
  • 非同步 — 原生非同步支援
  • 批次處理 — 同時處理多個輸入
  • 重試/備援 — 錯誤時自動重試
  • 追蹤 — 與LangSmith整合進行除錯

4.2. 核心元件

元件角色輸入 → 輸出
PromptTemplate使用變數格式化提示詞dict → PromptValue
ChatPromptTemplate格式化聊天訊息dict → ChatPromptValue
ChatModel呼叫LLM(ChatOpenAI、ChatNVIDIA...)PromptValue → AIMessage
StrOutputParser從AIMessage提取字串AIMessage → str
JsonOutputParser從輸出解析JSONAIMessage → dict
RunnablePassthrough不變地傳遞輸入any → any
RunnableLambda將函式包裝為Runnableany → any
RunnableParallel平行執行多個鏈dict → dict

4.3. LCEL管線流程


LCEL管線架構
════════════════════════════

簡單鏈:
─────────────
  {"topic": "AI"}
        │
        ▼
┌───────────────┐    ┌─────────────┐    ┌────────────────┐
│ PromptTemplate │──►│  ChatModel   │──►│ StrOutputParser │──► "AI is..."
│ "Explain {topic}"│  │ (ChatNVIDIA) │    │                │
└───────────────┘    └─────────────┘    └────────────────┘

       prompt      |      llm       |      parser
                   LCEL: prompt | llm | parser


平行鏈(RunnableParallel):
───────────────────────────────────
                 {"topic": "AI"}
                       │
              ┌────────┴────────┐
              ▼                 ▼
     ┌──────────────┐  ┌──────────────┐
     │  chain_summary│  │  chain_quiz  │
     │  prompt | llm │  │  prompt | llm│
     └──────┬───────┘  └──────┬───────┘
              │                 │
              └────────┬────────┘
                       ▼
            {"summary": "...", "quiz": "..."}

4.4. 程式碼:LCEL鏈


from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_nvidia_ai_endpoints import ChatNVIDIA

# 1. 初始化元件
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a {domain} expert. Answer concisely."),
    ("human", "{question}")
])

llm = ChatNVIDIA(
    model="meta/llama-3.1-8b-instruct",
    temperature=0.3,
    top_p=0.9,
    max_tokens=512
)

parser = StrOutputParser()

# 2. 使用LCEL管道語法建立鏈
chain = prompt | llm | parser

# 3. 同步呼叫
result = chain.invoke({
    "domain": "deep learning",
    "question": "How does Transformer self-attention work?"
})
print(result)

# 4. 串流(逐token)
for chunk in chain.stream({
    "domain": "deep learning",
    "question": "Compare RNN and Transformer"
}):
    print(chunk, end="", flush=True)

4.5. 進階:RunnableParallel與RunnableLambda


from langchain_core.runnables import (
    RunnablePassthrough,
    RunnableParallel,
    RunnableLambda
)

# 將自訂函式包裝為Runnable
def word_count(text: str) -> dict:
    return {"text": text, "word_count": len(text.split())}

# 平行鏈:同時摘要和計算字數
parallel_chain = RunnableParallel(
    summary=prompt | llm | parser,
    metadata=RunnableLambda(
        lambda x: f"Query: {x['question']}"
    )
)

# 帶passthrough的鏈——在管線中保留原始輸入
chain_with_context = (
    RunnablePassthrough.assign(
        answer=prompt | llm | parser
    )
)

# 平行呼叫
result = parallel_chain.invoke({
    "domain": "AI",
    "question": "What is Generative AI?"
})
# result = {"summary": "...", "metadata": "Query: What is Generative AI?"}

考試提示: 當考試給出LCEL程式碼並問「輸出類型是什麼?」時,逐步追蹤:PromptTemplate → PromptValue、ChatModel → AIMessage、StrOutputParser → str。如果忘記parser,輸出將是AIMessage物件(而非字串)。

5. 使用Gradio建構UI與使用LangServe建構API

5.1. Gradio:快速建構聊天機器人UI

Gradio讓你只需幾行程式碼就能為ML模型建立Web UI。gr.ChatInterface元件特別適合聊天機器人。


import gradio as gr
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_nvidia_ai_endpoints import ChatNVIDIA

# 設定鏈
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a friendly AI assistant."),
    ("human", "{message}")
])
llm = ChatNVIDIA(model="meta/llama-3.1-8b-instruct")
chain = prompt | llm | StrOutputParser()

# Gradio處理函式
def respond(message, history):
    """處理聊天訊息——history是[user, bot]配對的列表。"""
    response = chain.invoke({"message": message})
    return response

# 啟動UI
demo = gr.ChatInterface(
    fn=respond,
    title="NVIDIA NIM Chatbot",
    description="Chatbot powered by Llama 3.1 via NIM",
    examples=["What is Generative AI?", "Compare GAN and Diffusion"],
    theme="soft"
)
demo.launch(server_port=7860)

5.2. LangServe:將鏈暴露為REST API

LangServe將任何LCEL鏈轉換為REST API,並自動生成文件(Swagger)。適合生產環境部署。


# === 伺服器 (server.py) ===
from fastapi import FastAPI
from langserve import add_routes
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_nvidia_ai_endpoints import ChatNVIDIA

app = FastAPI(title="LLM API")

# 建立鏈
chain = (
    ChatPromptTemplate.from_messages([
        ("system", "AI assistant specializing in {domain}."),
        ("human", "{question}")
    ])
    | ChatNVIDIA(model="meta/llama-3.1-8b-instruct")
    | StrOutputParser()
)

# 在/chat端點暴露鏈
add_routes(app, chain, path="/chat")

# 執行: uvicorn server:app --port 8080

# === 客戶端 (client.py) ===
from langserve import RemoteRunnable

# 連接到LangServe端點
chain = RemoteRunnable("http://localhost:8080/chat")

# 像本地鏈一樣呼叫
result = chain.invoke({
    "domain": "machine learning",
    "question": "What is overfitting?"
})
print(result)

# 串流也能運作
for chunk in chain.stream({
    "domain": "NLP",
    "question": "How does tokenization work?"
}):
    print(chunk, end="")

Gradio + LangServe部署模式
═══════════════════════════════════════

   瀏覽器(使用者)          行動App / 服務
        │                          │
        ▼                          ▼
┌──────────────┐          ┌──────────────┐
│ Gradio UI     │          │ REST客戶端    │
│ :7860         │          │               │
└──────┬───────┘          └──────┬───────┘
       │                         │
       └────────┬────────────────┘
                ▼
      ┌──────────────────┐
      │  LangServe API    │
      │  FastAPI :8080    │
      │  /chat/invoke     │
      │  /chat/stream     │
      └────────┬─────────┘
               ▼
      ┌──────────────────┐
      │  LCEL鏈           │
      │  prompt|llm|parser│
      └────────┬─────────┘
               ▼
      ┌──────────────────┐
      │  NVIDIA NIM       │
      │  :8000            │
      └──────────────────┘

考試提示: Gradio = 原型/展示UI,LangServe = 生產REST API。如果考試問「最快展示聊天機器人的方式」→ Gradio。「為多個客戶端暴露鏈」→ LangServe。兩者可以搭配使用。

6. 對話管理與多輪對話

6.1. 記憶類型

聊天機器人需要記住先前對話輪次的上下文。LangChain提供多種記憶類型:

記憶類型運作方式優點缺點
ConversationBufferMemory儲存完整歷史記錄不遺失任何資訊Token數量快速增長
ConversationBufferWindowMemory保留最近N輪對話控制token使用量遺失較舊的上下文
ConversationSummaryMemory使用LLM摘要歷史記錄高效壓縮額外的LLM呼叫成本
ConversationSummaryBufferMemory摘要舊的 + 保留近期原始內容兼顧細節與壓縮較為複雜

6.2. 訊息類型

LangChain使用型別化訊息來區分角色:


from langchain_core.messages import (
    SystemMessage,
    HumanMessage,
    AIMessage
)

messages = [
    SystemMessage(content="You are an AI assistant."),
    HumanMessage(content="Hello!"),
    AIMessage(content="Hi there! How can I help?"),
    HumanMessage(content="Explain the attention mechanism"),
]

6.3. 程式碼:帶記憶的多輪聊天機器人


from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_nvidia_ai_endpoints import ChatNVIDIA

# 1. 帶歷史記錄插槽的提示詞
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are an AI assistant. Answer concisely."),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}")
])

llm = ChatNVIDIA(model="meta/llama-3.1-8b-instruct")
chain = prompt | llm | StrOutputParser()

# 2. 會話儲存——每個使用者有自己的歷史記錄
session_store = {}

def get_session_history(session_id: str):
    if session_id not in session_store:
        session_store[session_id] = InMemoryChatMessageHistory()
    return session_store[session_id]

# 3. 用訊息歷史包裝鏈
chain_with_history = RunnableWithMessageHistory(
    chain,
    get_session_history,
    input_messages_key="input",
    history_messages_key="history"
)

# 4. 聊天——相同的session_id保留上下文
config = {"configurable": {"session_id": "user-123"}}

r1 = chain_with_history.invoke(
    {"input": "My name is Minh"},
    config=config
)
print(r1)  # "Hello Minh!..."

r2 = chain_with_history.invoke(
    {"input": "What is my name?"},
    config=config
)
print(r2)  # "Your name is Minh."  ← 記住了上下文!

6.4. 視窗記憶模式


視窗記憶(k=3):僅保留最近3輪對話
═══════════════════════════════════════════════════════

第1輪: 使用者: "Hello"              ─┐
第2輪: AI: "Hi there!"               │ ← 當對話輪數 > 3+k 時被丟棄
第3輪: 使用者: "My name is Minh"     │
第4輪: AI: "Hello Minh!"            ─┘

第5輪: 使用者: "Explain CNN"         ─┐
第6輪: AI: "CNN is..."                │ ← 保留
第7輪: 使用者: "Compare with RNN?"   ─┘

發送的提示詞僅包含: [System] + [第5,6,7輪] + [第8輪輸入]
→ 節省token,但遺失「name is Minh」的上下文

考試提示: 「聊天機器人在幾輪後忘記上下文」→ 使用了太小的BufferWindowMemory或完全沒有記憶。「Token限制超出」→ 改用ConversationSummaryMemory來壓縮歷史記錄。

7. 推論框架比較

特性NVIDIA NIMvLLMTGI(HuggingFace)Ollama
後端TensorRT-LLMPagedAttentionPyTorch + Flashllama.cpp
需要GPUNVIDIA(A100/H100)NVIDIANVIDIA否(CPU可用)
吞吐量最高非常高高低
量化FP8、INT4內建AWQ、GPTQGPTQ、bitsandbytesGGUF
APIOpenAI相容OpenAI相容自訂 + MessagesOpenAI相容
設置Docker(NGC)pip installDocker1個二進位檔
最適合企業、生產環境研究、高吞吐量HF生態系本地開發、筆電
NVIDIA最佳化✅ 最深度✅ 良好部分❌

考試提示: NVIDIA DLI考試在所有生產部署問題中偏好NIM。「NVIDIA GPU上的最佳效能」→ NIM。「筆電上快速本地測試」→ Ollama。「開源高吞吐量」→ vLLM。

8. 速查表

概念重點
temperature = 0.0確定性輸出(可重現)
temperature = 1.0+創造性、更隨機
top_p = 0.1僅選擇最確定的token
top_k = 50限制為50個候選token
NIM預先最佳化容器、TensorRT-LLM、OpenAI API
LCEL pipeprompt | llm | parser
RunnableParallel同時執行多個鏈
Gradio展示UI、gr.ChatInterface
LangServe從LCEL鏈建立REST API、FastAPI
BufferMemory儲存完整歷史 → token數量快速增長
SummaryMemory使用LLM壓縮歷史 → 節省token
WindowMemory(k=N)保留最近N輪對話
MessagesPlaceholder提示詞中聊天歷史的插槽
RunnableWithMessageHistory包裝鏈 + 基於會話的記憶

9. 練習題

Q1:建構帶串流的LCEL鏈

撰寫一個LCEL鏈,使用PromptTemplate → ChatNVIDIA → StrOutputParser。提示詞接受一個topic並要求LLM解釋它。加入串流輸出。

顯示答案 Q1

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_nvidia_ai_endpoints import ChatNVIDIA

# 建立提示詞模板
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are an AI teacher. Explain clearly and simply."),
    ("human", "Explain in detail: {topic}")
])

# 建立LLM
llm = ChatNVIDIA(
    model="meta/llama-3.1-8b-instruct",
    temperature=0.5,
    max_tokens=1024
)

# 建立解析器
parser = StrOutputParser()

# LCEL鏈
chain = prompt | llm | parser

# 同步呼叫(一次返回完整結果)
result = chain.invoke({"topic": "Diffusion Models"})
print(result)

# 串流(逐token)——使用 .stream() 而非 .invoke()
for chunk in chain.stream({"topic": "Diffusion Models"}):
    print(chunk, end="", flush=True)

# 說明:
# - .invoke() 呼叫鏈並等待完整輸出
# - .stream() 返回迭代器,每個chunk是輸出的一部分
# - StrOutputParser允許串流,因為它直接傳遞字串chunk
# - 如果使用JsonOutputParser,串流將返回部分JSON

Q2:配置NIM並比較Temperature

使用OpenAI客戶端呼叫NIM端點。對同一個提示詞,比較temperature=0.0與temperature=1.0的輸出。每種配置執行3次並觀察差異。

顯示答案 Q2

from openai import OpenAI

# 連接到NIM端點
client = OpenAI(
    base_url="http://localhost:8000/v1",
    api_key="not-used"
)

prompt_msg = [
    {"role": "system", "content": "Answer concisely in 1-2 sentences."},
    {"role": "user", "content": "Why is the sky blue?"}
]

print("=== Temperature = 0.0(確定性)===")
for i in range(3):
    resp = client.chat.completions.create(
        model="meta/llama-3.1-8b-instruct",
        messages=prompt_msg,
        temperature=0.0,  # 始終選擇最高機率的token
        max_tokens=100
    )
    print(f"第{i+1}次: {resp.choices[0].message.content}")
# → 3次執行產生完全相同的輸出

print("\n=== Temperature = 1.0(創造性)===")
for i in range(3):
    resp = client.chat.completions.create(
        model="meta/llama-3.1-8b-instruct",
        messages=prompt_msg,
        temperature=1.0,  # 更寬的分佈,更隨機
        max_tokens=100
    )
    print(f"第{i+1}次: {resp.choices[0].message.content}")
# → 3次執行產生不同的輸出

# 關鍵要點:
# - temp=0.0:貪婪解碼,可重現,用於事實性任務
# - temp=1.0:更廣泛的取樣,有創造性,用於腦力激盪
# - NIM使用OpenAI相容API,所以客戶端程式碼完全相同

Q3:帶記憶的多輪聊天機器人

建立一個聊天機器人,使用ConversationBufferMemory透過RunnableWithMessageHistory整合到LCEL鏈中。機器人必須記住使用者在先前輪次中提到的名字。

顯示答案 Q3

from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.output_parsers import StrOutputParser
from langchain_core.chat_history import InMemoryChatMessageHistory
from langchain_core.runnables.history import RunnableWithMessageHistory
from langchain_nvidia_ai_endpoints import ChatNVIDIA

# 1. 帶歷史記錄佔位符的提示詞
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a friendly assistant. Remember information the user shares."),
    MessagesPlaceholder(variable_name="history"),
    ("human", "{input}")
])

# 2. 鏈
llm = ChatNVIDIA(model="meta/llama-3.1-8b-instruct", temperature=0.3)
chain = prompt | llm | StrOutputParser()

# 3. 會話儲存
store = {}
def get_history(session_id: str):
    if session_id not in store:
        store[session_id] = InMemoryChatMessageHistory()
    return store[session_id]

# 4. 用訊息歷史包裝
chatbot = RunnableWithMessageHistory(
    chain,
    get_history,
    input_messages_key="input",
    history_messages_key="history"
)

# 5. 測試多輪對話
cfg = {"configurable": {"session_id": "demo-001"}}

print(chatbot.invoke({"input": "My name is Lan"}, config=cfg))
# → "Hello Lan! Nice to meet you..."

print(chatbot.invoke({"input": "What is my name?"}, config=cfg))
# → "Your name is Lan." ← 機器人記住了上下文!

print(chatbot.invoke({"input": "I like machine learning"}, config=cfg))
# → "That's great, Lan! Machine learning is..."

# 檢查已儲存的歷史記錄
history = store["demo-001"]
for msg in history.messages:
    print(f"{msg.type}: {msg.content[:50]}...")

Q4:Gradio ChatInterface + LangChain

使用gr.ChatInterface建立Gradio UI聊天機器人,後端使用呼叫NIM的LCEL鏈。支援串流回應。

顯示答案 Q4

import gradio as gr
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_nvidia_ai_endpoints import ChatNVIDIA

# 1. 設定LCEL鏈
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are an AI assistant specializing in deep learning."),
    ("human", "{message}")
])
llm = ChatNVIDIA(
    model="meta/llama-3.1-8b-instruct",
    temperature=0.7
)
chain = prompt | llm | StrOutputParser()

# 2. Gradio的串流處理函式
def respond_stream(message, history):
    """
    Gradio ChatInterface呼叫此函式。
    - message: 使用者的新訊息
    - history: [user_msg, bot_msg]配對的列表
    每個chunk都yield,讓Gradio即時顯示串流。
    """
    partial = ""
    for chunk in chain.stream({"message": message}):
        partial += chunk
        yield partial  # Gradio在每次yield時更新UI

# 3. 啟動Gradio應用
demo = gr.ChatInterface(
    fn=respond_stream,
    title="🤖 DL Assistant (NIM-powered)",
    description="Ask anything about Deep Learning",
    examples=[
        "How does Transformer work?",
        "Compare CNN and ViT",
        "What is Batch Normalization used for?"
    ],
    theme="soft"
)

demo.launch(server_port=7860, share=False)

# 存取: http://localhost:7860
# Gradio將即時顯示串流回應

Q5:除錯——鏈返回空輸出

以下程式碼可以執行,但輸出總是空的或是意外的物件。找出並修復錯誤。


# BUG: chain returns AIMessage object instead of string
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import JsonOutputParser  # ← Hmm...
from langchain_nvidia_ai_endpoints import ChatNVIDIA

prompt = ChatPromptTemplate.from_messages([
    ("system", "Answer concisely in plain text."),
    ("human", "{question}")
])

llm = ChatNVIDIA(model="meta/llama-3.1-8b-instruct")
chain = prompt | llm | JsonOutputParser()  # ← Bug is here

result = chain.invoke({"question": "What is AI?"})
print(result)  # → Error or empty/weird output
顯示答案 Q5

# 錯誤分析:
# - 提示詞要求LLM以純文字回答
# - 但解析器是JsonOutputParser → 期望JSON格式
# - LLM返回 "AI is artificial intelligence..."(非JSON)
# - JsonOutputParser嘗試解析 → 失敗或返回空輸出

# 修復:將JsonOutputParser替換為StrOutputParser

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser  # ← 修復!
from langchain_nvidia_ai_endpoints import ChatNVIDIA

prompt = ChatPromptTemplate.from_messages([
    ("system", "Answer concisely in plain text."),
    ("human", "{question}")
])

llm = ChatNVIDIA(model="meta/llama-3.1-8b-instruct")
chain = prompt | llm | StrOutputParser()  # ← StrOutputParser

result = chain.invoke({"question": "What is AI?"})
print(result)  # → "AI (Artificial Intelligence) is..."

# 規則:OutputParser類型必須與輸出格式匹配:
# - 純文字 → StrOutputParser
# - JSON輸出(提示詞必須要求JSON)→ JsonOutputParser
# - 結構化輸出 → PydanticOutputParser
# 如果不匹配 → 鏈會靜默失敗或拋出錯誤