iT邦幫忙

2025 iThome 鐵人賽

DAY 2
0

專案變更總覽(你要做什麼)
✅ 新增檔案

src/aiClient.js:集中管理 OpenAI Client(之後各天都共用)。

src/promptPresets.js:常用提示詞模板(語氣、長度、格式控制)。

src/day2_text_generation.js:Day 2 主程式,參數化文字生成。

index.js:指令列入口,可選要跑哪個 day(今天預設跑 Day 2)。

♻️ 修改檔案

package.json:加入方便的 npm run day2 指令。

🗑️ 需要刪除 / 改名(擇一)

你昨天若只留有 index.js(Day 1 測試檔):

建議改名成 src/day1_hello.js 保留紀錄;

或者你不想留:就刪除舊的 index.js(因為今天會放新的 index.js)。

若選擇保留 Day1:把昨天 index.js 檔名改為 src/day1_hello.js 即可,內容不用改。


新增/修改的程式碼

1) src/aiClient.js(新增)

集中管理 OpenAI Client,之後每天都能重用。

// src/aiClient.js
import OpenAI from "openai";
import dotenv from "dotenv";

dotenv.config();

if (!process.env.OPENAI_API_KEY) {
  throw new Error("OPENAI_API_KEY 未設定,請在 .env 加上你的金鑰。");
}

export const openai = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

2) src/promptPresets.js(新增)

整理可重用的 Prompt 模板與控制規則。

// src/promptPresets.js

// 長度控制說明
const LENGTH_HINT = {
  short: "請控制在 80~120 字之內。",
  medium: "請控制在 200~300 字之內。",
  long: "請控制在 500~800 字之內,分段清楚。",
};

// 語氣控制說明
const TONE_HINT = {
  friendly: "語氣親切、鼓勵式、貼近讀者。",
  professional: "語氣專業、精煉,避免口語化。",
  persuasive: "語氣具說服力,強調價值與行動。",
};

// 輸出格式控制
const FORMAT_HINT = {
  plain: "以一般段落輸出,不需要條列。",
  bullets: "請用條列式(- 開頭)輸出重點,最多 5 點。",
  markdown: "請使用 Markdown 標題與小節清楚呈現。",
};

// 可選的主題預設
export const PRESETS = {
  blog: ({ topic, tone, length, format }) => `
你是一位資深技術部落客,請針對「${topic}」寫一段短文。
${TONE_HINT[tone] ?? ""} 
${LENGTH_HINT[length] ?? ""} 
${FORMAT_HINT[format] ?? ""} 
請避免虛構數據,聚焦實務價值。`,
  diary: ({ topic, tone, length, format }) => `
你是一位工程師,正在撰寫今日學習日誌,主題:「${topic}」。
${TONE_HINT[tone] ?? ""} 
${LENGTH_HINT[length] ?? ""} 
${FORMAT_HINT[format] ?? ""} 
請包含:今天做了什麼、遇到的問題、下一步計畫。`,
  product: ({ topic, tone, length, format }) => `
你是產品行銷,請為「${topic}」撰寫產品簡介。
${TONE_HINT[tone] ?? ""} 
${LENGTH_HINT[length] ?? ""} 
${FORMAT_HINT[format] ?? ""} 
請包含:目標用戶、核心賣點、使用情境、CTA。`,
};

export const defaultParams = {
  tone: "friendly",      // friendly | professional | persuasive
  length: "medium",      // short | medium | long
  format: "markdown",    // plain | bullets | markdown
  preset: "blog",        // blog | diary | product
};

3) src/day2_text_generation.js(新增)

Day 2 主程式:把參數轉成可控輸出。

// src/day2_text_generation.js
import { openai } from "./aiClient.js";
import { PRESETS, defaultParams } from "./promptPresets.js";

/**
 * 產生文字
 * @param {Object} opts
 * @param {string} opts.topic - 文章主題
 * @param {string} [opts.preset] - 使用哪個模板(blog/diary/product)
 * @param {string} [opts.tone] - 語氣(friendly/professional/persuasive)
 * @param {string} [opts.length] - 長度(short/medium/long)
 * @param {string} [opts.format] - 輸出格式(plain/bullets/markdown)
 * @param {number} [opts.temperature] - 0~2,越高越有創意
 * @param {number} [opts.max_tokens] - 生成的最大 token 數
 */
export async function generateText(opts = {}) {
  const {
    topic = "今天的學習",
    preset = defaultParams.preset,
    tone = defaultParams.tone,
    length = defaultParams.length,
    format = defaultParams.format,
    temperature = 0.7,
    max_tokens = 600,
  } = opts;

  const builder = PRESETS[preset];
  if (!builder) throw new Error(`未知的 preset:${preset}`);

  const userPrompt = builder({ topic, tone, length, format });

  const response = await openai.chat.completions.create({
    model: "gpt-4o-mini",
    temperature,
    max_tokens,
    messages: [
      {
        role: "system",
        content:
          "你是嚴謹可靠的中文寫作助手,輸出需具可讀性與實用性,避免編造事實。",
      },
      { role: "user", content: userPrompt },
    ],
  });

  return response.choices?.[0]?.message?.content?.trim() ?? "";
}

4) index.js(新增/覆蓋)

提供 CLI 參數(之後也能當作 Node 腳本被 Next.js API 呼叫)。

// index.js
import { generateText } from "./src/day2_text_generation.js";

// 解析簡易 CLI 參數
// 範例:node index.js --topic "RAG 入門" --preset blog --tone professional --length short --format markdown --temp 0.6
const args = Object.fromEntries(
  process.argv.slice(2).reduce((acc, cur, i, arr) => {
    if (cur.startsWith("--")) {
      const key = cur.replace(/^--/, "");
      const val = arr[i + 1] && !arr[i + 1].startsWith("--") ? arr[i + 1] : true;
      acc.push([key, val]);
    }
    return acc;
  }, [])
);

async function main() {
  const content = await generateText({
    topic: args.topic || "生成式 AI 實戰挑戰 Day 2",
    preset: args.preset || "blog",
    tone: args.tone || "friendly",
    length: args.length || "medium",
    format: args.format || "markdown",
    temperature: args.temp ? Number(args.temp) : 0.7,
    max_tokens: args.max_tokens ? Number(args.max_tokens) : 600,
  });

  console.log("\n=== 生成內容 ===\n");
  console.log(content);
}

main().catch((e) => {
  console.error("發生錯誤:", e.message);
  process.exit(1);
});

5) package.json(修改)

加入快捷指令(保留你原本的內容,只需新增 scripts)。

{
  "scripts": {
    "day2": "node index.js --topic \"RAG 入門\" --preset blog --tone professional --length short --format markdown --temp 0.6"
  }
}

你也可以隨時換參數,例如:

npm run day2 -- --topic "用 LLM 做學習日誌" --preset diary --tone friendly --length medium --format bullets --temp 0.7

如何執行(測試)

# 推薦先把 Day1 改名保存(可選)
# mv index.js src/day1_hello.js

node index.js --topic "用 LLM 生產部落格文章的注意事項" --preset blog --tone professional --length medium --format markdown --temp 0.5

你會得到一段 可控長度、語氣、格式 的 Markdown 文章。
(temp 越低越穩定,越高越有創意;max_tokens 可避免生成過多。)


上一篇
📅 Day 1:挑戰開始!設定目標與環境準備
下一篇
Day 3:提示工程(Prompt Engineering)
系列文
30 天生成式 AI 實戰挑戰:從基礎到應用4
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言