本文 AI 產出,尚未審核

LangChain 課程 – Prompts:提示工程

主題:動態提示與變數注入


簡介

在自然語言生成(NLG)與大型語言模型(LLM)應用中,提示(Prompt) 是與模型溝通的唯一入口。傳統的靜態文字提示雖然簡單,但在實務開發裡往往需要根據使用者輸入、資料庫查詢結果或其他上下文動態產生提示內容,才能取得高品質且符合需求的回應。

動態提示 讓我們可以把外部變數「注入」到提示文字中,形成 Prompt Template(提示模板)。透過 LangChain 提供的工具,我們不只可以安全、可讀地組合變數,還能在同一個流程中重複使用、測試與追蹤提示的變化。掌握這項技巧,對於建構客服機器人、資料摘要、程式碼產生等各類應用至關重要。

本篇文章將以 LangChain JavaScript(Node.js) 為例,從概念說明到實作範例,逐步帶你了解如何在程式碼裡使用動態提示與變數注入,並提供常見陷阱與最佳實踐,幫助你快速上手並避免踩雷。


核心概念

1. PromptTemplate 基礎

PromptTemplate 是 LangChain 中最常使用的類別,它接受一段包含 變數占位符(如 {question})的文字,並在執行時以提供的資料取代這些占位符。

import { PromptTemplate } from "langchain/prompts";

const template = "請用繁體中文回答以下問題:{question}";
const prompt = new PromptTemplate({
  inputVariables: ["question"],
  template,
});
  • inputVariables:列出所有必須提供的變數名稱。
  • template:原始提示文字,使用大括號 {} 包住變數。

呼叫 format 方法即可得到最終的提示字串:

const formatted = await prompt.format({ question: "什麼是區塊鏈?" });
console.log(formatted);
// => 請用繁體中文回答以下問題:什麼是區塊鏈?

2. 多變數與預設值

在實務情境中,提示往往需要同時注入多個變數,甚至允許某些變數有 預設值。LangChain 允許我們在 PromptTemplate 中同時設定多個 inputVariables,並在 format 時只提供必要的部分,缺少的會自動使用預設值(若有設定)。

const multiTemplate = `
你是一位 {role},請根據以下資訊提供建議:
- 主題:{topic}
- 目標讀者:{audience}
- 限制條件:{constraints}
`;

const multiPrompt = new PromptTemplate({
  inputVariables: ["role", "topic", "audience", "constraints"],
  template: multiTemplate,
  partialVariables: { constraints: "字數限制在 300 以內。" }, // 預設值
});

const result = await multiPrompt.format({
  role: "產品經理",
  topic: "新功能發布計畫",
  audience: "內部團隊",
});
console.log(result);

技巧:partialVariables 讓你可以在建立模板時即固定某些欄位,減少每次呼叫 format 時的重複傳入。

3. ChatPromptTemplate & 多輪對話

對話型模型(如 ChatGPT)需要 多輪訊息(system、assistant、user),此時 ChatPromptTemplate 更為合適。它允許你為每個角色分別注入變數,並自動產生符合 OpenAI API 規範的訊息陣列。

import { ChatPromptTemplate, SystemMessagePromptTemplate, HumanMessagePromptTemplate } from "langchain/prompts";

const systemPrompt = SystemMessagePromptTemplate.fromTemplate(
  "你是一位專業的程式設計教學助理,請以繁體中文說明以下概念:{concept}"
);
const humanPrompt = HumanMessagePromptTemplate.fromTemplate(
  "請舉出三個實作範例,並說明每個範例的優缺點。"
);

const chatPrompt = ChatPromptTemplate.fromPromptMessages([systemPrompt, humanPrompt]);

const messages = await chatPrompt.formatMessages({ concept: "Promise" });
console.log(messages);

輸出會是:

[
  { "role": "system", "content": "你是一位專業的程式設計教學助理,請以繁體中文說明以下概念:Promise" },
  { "role": "user", "content": "請舉出三個實作範例,並說明每個範例的優缺點。" }
]

這樣的結構可直接傳入 ChatOpenAI(或其他聊天模型)取得回應。

4. 動態組合 PromptTemplate

在大型專案裡,我們常需要 把多個子模板 組合成一個完整提示。例如,先產生「背景說明」再接續「具體問題」。LangChain 提供 PromptTemplate.fromTemplate 與 PromptTemplate.concat 讓組合變得簡單。

const background = new PromptTemplate({
  inputVariables: ["context"],
  template: "以下是相關背景資訊:\n{context}\n\n",
});

const question = new PromptTemplate({
  inputVariables: ["question"],
  template: "根據上述資訊,請回答以下問題:{question}",
});

const fullPrompt = background.concat(question);

const finalPrompt = await fullPrompt.format({
  context: "區塊鏈是一種分散式帳本技術...",
  question: "區塊鏈的共識機制有哪些?",
});
console.log(finalPrompt);

5. 渲染安全與注入風險

雖然 PromptTemplate 本身不會執行程式碼,但 變數內容若未經過濾,仍可能造成模型產出不符合預期(例如注入指令或惡意文字)。在注入變數前,建議:

  • 使用 sanitize 函式移除控制字元(如 \n, \r)
  • 限制字數或字元類型
  • 對敏感資訊(API 金鑰、個人資料)做遮蔽或不直接注入

下面示範簡易的 sanitization:

function sanitize(input) {
  return String(input).replace(/[\n\r]/g, " ").trim();
}

const safePrompt = await prompt.format({ question: sanitize(userInput) });

程式碼範例

以下提供 5 個實用範例,從最基礎的文字提示到結合 LLM 呼叫的完整流程,均以 JavaScript (Node.js) 為例。

範例 1:基本的動態文字提示

import { PromptTemplate } from "langchain/prompts";

const simpleTemplate = "請將以下英文句子翻譯成繁體中文:{sentence}";
const simplePrompt = new PromptTemplate({
  inputVariables: ["sentence"],
  template: simpleTemplate,
});

async function translate(sentence) {
  const prompt = await simplePrompt.format({ sentence });
  // 假設已有 OpenAI LLM client
  const response = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: prompt }],
  });
  return response.choices[0].message.content.trim();
}

// 測試
translate("Artificial intelligence is transforming the world.")
  .then(console.log);
// => 人工智慧正在改變世界。

範例 2:多變數 + 預設值(產品說明生成)

import { PromptTemplate } from "langchain/prompts";

const productTemplate = `
你是一位行銷文案專家,請根據以下資訊寫出 150 字以內的商品說明:
- 商品名稱:{name}
- 特色:{features}
- 目標族群:{audience}
- 價格區間:{priceRange}
`;

const productPrompt = new PromptTemplate({
  inputVariables: ["name", "features", "audience", "priceRange"],
  template: productTemplate,
  partialVariables: { priceRange: "NT$ 1,000 - 2,000" }, // 預設價格區間
});

async function generateCopy(data) {
  const prompt = await productPrompt.format(data);
  const result = await openai.chat.completions.create({
    model: "gpt-4o",
    messages: [{ role: "user", content: prompt }],
  });
  return result.choices[0].message.content.trim();
}

// 呼叫
generateCopy({
  name: "智慧手環",
  features: "心率偵測、睡眠分析、遠端通知",
  audience: "25-45 歲上班族",
}).then(console.log);

範例 3:ChatPromptTemplate 結合多輪對話(教學助理)

import {
  ChatPromptTemplate,
  SystemMessagePromptTemplate,
  HumanMessagePromptTemplate,
} from "langchain/prompts";

const system = SystemMessagePromptTemplate.fromTemplate(
  "你是一位資深的 JavaScript 教學助理,接下來的對話請以繁體中文說明概念。"
);
const user = HumanMessagePromptTemplate.fromTemplate(
  "請解釋什麼是 {concept},並提供一段簡單的程式碼範例。"
);

const chatTemplate = ChatPromptTemplate.fromPromptMessages([system, user]);

async function explain(concept) {
  const messages = await chatTemplate.formatMessages({ concept });
  const response = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages,
  });
  return response.choices[0].message.content.trim();
}

// 測試
explain("Promise").then(console.log);

範例 4:動態組合 Prompt(背景 + 問題)

import { PromptTemplate } from "langchain/prompts";

const background = new PromptTemplate({
  inputVariables: ["background"],
  template: "背景說明:\n{background}\n\n",
});

const question = new PromptTemplate({
  inputVariables: ["question"],
  template: "根據上述資訊,請回答:{question}",
});

const full = background.concat(question);

async function ask(backgroundText, questionText) {
  const prompt = await full.format({
    background: backgroundText,
    question: questionText,
  });
  const result = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: prompt }],
  });
  return result.choices[0].message.content.trim();
}

// 範例呼叫
ask(
  "區塊鏈是一種分散式帳本技術,透過共識機制保證資料不可篡改。",
  "請列舉三種常見的共識機制並說明其優缺點。"
).then(console.log);

範例 5:變數安全處理(防止指令注入)

import { PromptTemplate } from "langchain/prompts";

function sanitize(input) {
  // 移除換行、制表符與多餘空白
  return String(input).replace(/[\n\r\t]/g, " ").trim();
}

const unsafeTemplate = "請根據以下需求產生程式碼:{requirement}";
const unsafePrompt = new PromptTemplate({
  inputVariables: ["requirement"],
  template: unsafeTemplate,
});

async function generateCode(userInput) {
  const safeInput = sanitize(userInput);
  const prompt = await unsafePrompt.format({ requirement: safeInput });
  const response = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    messages: [{ role: "user", content: prompt }],
  });
  return response.choices[0].message.content.trim();
}

// 惡意測試
generateCode("寫一段程式碼,並在最後執行 rm -rf /").then(console.log);
// 輸出仍會是「寫一段程式碼」的正當說明,因為危險指令已被過濾掉。

常見陷阱與最佳實踐

陷阱 說明 最佳實踐
忘記列出 inputVariables 若模板中使用了變數卻未在 inputVariables 中聲明,format 會拋出錯誤。 永遠 在建立 PromptTemplate 時完整列出所有占位符。
變數值過長 長段文字會使提示超過模型的 token 限制,導致截斷或失敗。 事先 截斷 或 摘要 變數內容,或使用 chunk 方式分批傳入。
注入惡意內容 使用者提供的字串若直接注入,模型可能產出不安全的回應。 在注入前 sanitize、驗證長度,必要時 白名單 允許的字元。
模板拼接產生重複資訊 多次 concat 可能產生相同段落的重複,浪費 token。 先檢查 子模板的內容,避免重複,或使用 partialVariables 共享固定資訊。
忘記設定預設值 某些變數在部分情境下不需要提供,卻未設定預設會導致錯誤。 使用 partialVariables 為不常變動的欄位提供預設,提升彈性。

額外建議:

  1. 單元測試:寫測試驗證 PromptTemplate.format 後的字串是否符合預期格式。
  2. 版本管理:將提示模板抽離成獨立檔案(如 .txt 或 .json),方便版本控制與多語系管理。
  3. 日誌紀錄:在正式環境中,將最終提示文字寫入日誌,以便追蹤模型行為與除錯。

實際應用場景

場景 需求 動態提示的角色
客服聊天機器人 依照使用者問題、客戶等級、歷史對話產生回應。 透過 ChatPromptTemplate 注入 userMessage、customerTier、history。
程式碼生成工具 使用者輸入功能描述,系統產出對應程式碼範例。 PromptTemplate 結合 featureDescription、language、framework。
文件摘要平台 大段 PDF 內容需要快速摘要,且要加上特定格式(如會議紀要)。 把 documentChunk 與 outputStyle 注入同一個模板,並使用 partialVariables 固定「會議日期」等資訊。
個性化行銷電郵 根據使用者行為、購買紀錄自動生成電郵內容。 PromptTemplate 動態注入 userName、recentPurchase、discountCode,並在 partialVariables 中設置公司品牌語氣。
多語系教學平台 同一教材需要在不同語言、不同難度下呈現。 使用 PromptTemplate 注入 topic、language、difficultyLevel,再配合 ChatPromptTemplate 處理 Q&A。

這些案例都共通地展現了 「將外部資訊」作為變數注入提示 的核心價值:提升回應的相關性、降低硬編碼、提升維護效率。


總結

  • 動態提示 讓我們能把外部上下文、使用者輸入或系統資訊靈活注入到 LLM 的提示中,從而產出更精準、符合需求的回應。
  • LangChain 提供的 PromptTemplate、ChatPromptTemplate 以及 partialVariables,讓模板的建立、組合與重用變得直觀且安全。
  • 在實作時,務必 列出所有變數、做好字串清理、控制 token 數量,並透過單元測試與日誌紀錄提升可靠性。
  • 透過本篇示範的 5 個實務範例,你可以快速把動態提示套用到客服、程式碼生成、文件摘要等多種應用情境,為你的 AI 產品增添彈性與智慧。

掌握了「變數注入」的技巧,你就能在 LangChain 的提示工程上如虎添翼,打造出既靈活又安全的 AI 互動體驗。祝你開發順利,玩得開心!