本文 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.fromTemplatePromptTemplate.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 注入 userMessagecustomerTierhistory
程式碼生成工具 使用者輸入功能描述,系統產出對應程式碼範例。 PromptTemplate 結合 featureDescriptionlanguageframework
文件摘要平台 大段 PDF 內容需要快速摘要,且要加上特定格式(如會議紀要)。 documentChunkoutputStyle 注入同一個模板,並使用 partialVariables 固定「會議日期」等資訊。
個性化行銷電郵 根據使用者行為、購買紀錄自動生成電郵內容。 PromptTemplate 動態注入 userNamerecentPurchasediscountCode,並在 partialVariables 中設置公司品牌語氣。
多語系教學平台 同一教材需要在不同語言、不同難度下呈現。 使用 PromptTemplate 注入 topiclanguagedifficultyLevel,再配合 ChatPromptTemplate 處理 Q&A。

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


總結

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

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