Try   HackMD

第二堂:LLM API 與 Node.js 整合運用

辨識圖片資訊(api)

/**
 * 處理圖片辨識查詢
 * @param {string} query - 客戶的查詢
 * @param {string} imageUrl - 圖片URL
 * @returns {Promise<string>} - 包含回答的文字
 */
async function processImageQuery(query, imageUrl) {
  try {
    console.log(`處理圖片查詢: "${query}", 圖片: ${imageUrl}`);

    // 準備輸入
    const input = [
      {
        role: "user",
        content: [
          { type: "input_text", text: query || "這張圖片中有什麼?" },
          { type: "input_image", image_url: imageUrl },
        ],
      },
    ];

    // 發送 AI 請求
    const response = await openai.responses.create({
      model: "gpt-4o",
      input: input,
    });

    console.log("AI 回應:", response);

    // 返回 AI 回答
    return response.output_text;
  } catch (error) {
    console.error("處理圖片查詢出錯:", error);
    throw error;
  }
}

// API 端點 - 圖片辨識
app.post("/image-analysis", async (req, res) => {
  try {
    const { message, imageUrl } = req.body;

    if (!imageUrl) {
      return res.status(400).json({ error: "請提供圖片URL" });
    }

    const answer = await processImageQuery(message, imageUrl);

    if (!answer) {
      return res.status(500).json({ error: "無法生成回答" });
    }

    res.json({ response: answer });
  } catch (error) {
    console.error("API 錯誤:", error);
    res.status(500).json({ error: error.message || "處理圖片查詢時發生錯誤" });
  }
});

電商生成產品類別 + function calling


const PRODUCT_CATEGORIES = ["男裝","女裝","鞋子","3C","玩具","遊戲","煙酒","家具"]
/**
 * 生成商品分類標籤
 * @param {string} imageUrl - 圖片URL
 * @returns {Promise<Object>} - 包含標籤的對象
 */
async function generateProductCategories(imageUrl) {
  try {
    console.log(`生成商品分類標籤,圖片: ${imageUrl}`);

    // 定義可用的函式
    const tools = [
      {
        type: "function",
        name: "select_product_categories",
        description:
          "從預定義的商品分類標籤中選擇最適合的標籤,所有回應必須使用繁體中文",
        parameters: {
          type: "object",
          properties: {
            product_name: {
              type: "string",
              description: "產品名稱(必須使用繁體中文)",
            },
            product_description: {
              type: "string",
              description: "產品的簡短描述(必須使用繁體中文)",
            },
            categories: {
              type: "array",
              description:
                "從預定義分類中選擇的商品分類標籤,最多選擇5個最相關的標籤",
              items: {
                type: "string",
                enum: PRODUCT_CATEGORIES,
              },
              maxItems: 5,
            },
          },
          required: ["product_name", "product_description", "categories"],
        },
      },
    ];

    // 準備輸入
    const input = [
      {
        role: "user",
        content: [
          {
            type: "input_text",
            text: "請分析這張產品圖片,從預定義的商品分類標籤中選擇最適合的標籤。請選擇最多5個最相關的標籤,並提供簡短的產品描述。請務必使用繁體中文回答,產品名稱和描述都必須是繁體中文。",
          },
          { type: "input_image", image_url: imageUrl },
        ],
      },
    ];

    // 發送 AI 請求
    const response = await openai.responses.create({
      model: "gpt-4o",
      input: input,
      tools: tools,
      tool_choice: "auto",
    });

    console.log("AI 回應:", response);

    // 檢查是否有函式被呼叫
    if (response.output && response.output.length > 0) {
      const functionCall = response.output.find(
        (call) => call.name === "select_product_categories"
      );

      if (functionCall) {
        // 解析函式參數
        const args = JSON.parse(functionCall.arguments);
        return {
          success: true,
          data: args,
        };
      }
    }

    // 如果沒有函式呼叫,返回錯誤
    return {
      success: false,
      error: "無法生成標籤",
    };
  } catch (error) {
    console.error("生成商品分類標籤出錯:", error);
    return {
      success: false,
      error: error.message,
    };
  }
}

// API 端點 - 商品分類標籤
app.post("/product-categories", async (req, res) => {
  try {
    const { imageUrl } = req.body;

    if (!imageUrl) {
      return res.status(400).json({ error: "請提供圖片URL" });
    }

    const result = await generateProductCategories(imageUrl);

    if (!result.success) {
      return res.status(500).json({ error: result.error || "無法生成標籤" });
    }

    res.json(result);
  } catch (error) {
    console.error("API 錯誤:", error);
    res.status(500).json({ error: error.message || "處理圖片標籤時發生錯誤" });
  }
});

產圖服務(api)

OpenAI 的圖像 API 提供以下三大功能:

  1. 圖像生成(Image Generation):根據文字提示(prompt)生成全新圖像。
  2. 圖像編輯(Image Editing):對現有圖像進行編輯,需提供原始圖像與遮罩(mask)。
  3. 圖像變體(Image Variations):基於現有圖像創建不同的變體。

二、主要參數說明

參數 類型 必填 說明
prompt string 描述希望生成圖像的文字提示。
model string 使用的模型,預設為 dall-e-2
n integer 要生成的圖像數量,dall-e-2 支援 1~10,dall-e-3 僅支援 1。
size string 圖像尺寸,dall-e-2 支援 256x256512x5121024x1024dall-e-3 支援 1024x10241792x10241024x1792
response_format string 回應格式,預設為 url,也可設為 b64_json 以獲取 base64 編碼的圖像。
user string 用戶唯一標識符,有助於監控和濫用檢測。
quality string 圖像品質,dall-e-3 可設為 standardhd
style string 圖像風格,dall-e-3 可設為 vivid(生動)或 natural(自然)。

req body
{
"prompt": "一隻可愛的白色暹羅貓坐在窗台上,窗外是美麗的日落風景",
"size": "1024x1024"
}

/**
 * 生成圖片
 * @param {string} prompt - 圖片描述提示
 * @param {string} size - 圖片尺寸
 * @returns {Promise<string>} - 生成的圖片URL
 */
async function generateImage(prompt, size = "1024x1024") {
  try {
    console.log(`生成圖片: "${prompt}", 尺寸: ${size}`);

    const response = await openai.images.generate({
      model: "dall-e-3",
      prompt: prompt,
      n: 1,
      size: size,
      quality: "standard",
    });

    console.log("圖片生成回應:", response);

    // 回傳生成的圖片URL
    if (response.data && response.data.length > 0) {
      return {
        url: response.data[0].url,
        revised_prompt: response.data[0].revised_prompt,
      };
    } else {
      throw new Error("無法生成圖片");
    }
  } catch (error) {
    console.error("生成圖片出錯:", error);
    throw error;
  }
}

// API 端點 - 圖片生成
app.post("/image-generation", async (req, res) => {
  try {
    const { prompt, size } = req.body;

    if (!prompt) {
      return res.status(400).json({ error: "請提供圖片描述提示" });
    }

    const validSizes = ["1024x1024", "1024x1792", "1792x1024"];
    const imageSize = validSizes.includes(size) ? size : "1024x1024";

    const result = await generateImage(prompt, imageSize);

    res.json({
      image_url: result.url,
      revised_prompt: result.revised_prompt,
    });
  } catch (error) {
    console.error("API 錯誤:", error);
    res.status(500).json({ error: error.message || "生成圖片時發生錯誤" });
  }
});

AI 天氣助手(API天氣 API)

截圖 2025-04-07 下午5.12.08

環境

// 天氣 API 配置
const axios = require("axios");
const WEATHER_API_KEY = process.env.WEATHERAPI_KEY;
const WEATHER_BASE_URL = "https://api.weatherapi.com/v1";

/**
 * 取得天氣數據
 * @param {string} city - 城市名稱
 * @param {number} days - 預報天數 (1-10)
 * @returns {Promise<Object>} - 天氣數據
 */
async function getWeatherData(city, days = 7) {
  try {
    const response = await axios.get(`${WEATHER_BASE_URL}/forecast.json`, {
      params: {
        key: WEATHER_API_KEY,
        q: city,
        days: days,
        aqi: "yes",
        alerts: "yes",
        lang: "zh_tw",
      },
    });
    return response.data;
  } catch (error) {
    console.error("取得天氣數據時出錯:", error);
    throw error;
  }
}

/**
 * 根據城市名稱取得天氣
 * @param {string} city - 城市名稱
 * @returns {Promise<Object>} - 天氣數據
 */
async function getWeatherByCity(city) {
  try {
    console.log(`取得城市天氣: ${city}`);
    const weatherData = await getWeatherData(city);
    return weatherData;
  } catch (error) {
    console.error("根據城市取得天氣時出錯:", error);
    throw error;
  }
}

/**
 * 處理天氣查詢
 * @param {string} query - 用戶的查詢
 * @returns {Promise<Object>} - 包含回答和天氣數據的對象
 */
async function processWeatherQuery(query) {
  try {
    // 定義可用的函式
    const tools = [
      {
        type: "function",
        name: "get_weather",
        description: "取得指定城市的天氣信息",
        parameters: {
          type: "object",
          properties: {
            city: {
              type: "string",
              description: "城市名稱,例如 taipei, tokyo, new york",
            },
            days: {
              type: "integer",
              description: "預報天數 (1-10)",
            },
          },
          required: ["city"],
        },
      },
    ];

    // 準備輸入
    const input = [{ role: "user", content: query }];
    console.log(`處理天氣查詢: "${query}"`);

    // 第一次 AI 回應
    const response = await openai.responses.create({
      model: "gpt-4o",
      instructions: `你是一個天氣助手,可以回答用戶關於天氣的問題。
當用戶詢問天氣相關問題時,呼叫 get_weather 函式取得天氣數據。
使用繁體中文回答,簡潔友善。`,
      input,
      tools,
      tool_choice: "auto",
    });

    // 檢查是否有函式被呼叫
    if (response.output && response.output.length > 0) {
      // 檢查是否是天氣查詢
      const weatherCall = response.output.find(
        (call) => call.name === "get_weather"
      );

      if (weatherCall) {
        // 取得天氣數據
        const args = JSON.parse(weatherCall.arguments);
        const city = args.city || "taipei";
        const days = args.days || 7;

        console.log(`取得城市 "${city}" 的天氣數據`);
        const weatherData = await getWeatherByCity(city);

        // 將結果傳回 AI
        input.push(weatherCall);
        input.push({
          type: "function_call_output",
          call_id: weatherCall.call_id,
          output: JSON.stringify(weatherData),
        });

        // 取得最終回答
        const finalResponse = await openai.responses.create({
          model: "gpt-4o",
          instructions: `你是一個天氣助手,根據提供的天氣數據回答用戶問題。
使用繁體中文回答,簡潔友善。
如果用戶問的是預測性問題(如"會下雨嗎"),根據降雨機率和降水量做出合理判斷。`,
          input,
        });

        return {
          query,
          city,
          answer: finalResponse.output_text,
          weatherData,
        };
      }
    }

    // 如果沒有函式呼叫,直接返回 AI 回答
    return {
      query,
      answer: response.output_text,
      weatherData: null,
    };
  } catch (error) {
    console.error("處理天氣查詢出錯:", error);
    throw error;
  }
}

// API 端點 - 處理天氣查詢
app.post("/api/weather/query", async (req, res) => {
  try {
    const { query } = req.body;

    if (!query) {
      return res.status(400).json({ error: "請提供查詢內容" });
    }

    console.log(`收到天氣查詢: "${query}"`);
    const result = await processWeatherQuery(query);

    if (!result.answer) {
      return res.status(500).json({ error: "無法生成回答" });
    }

    res.json(result);
  } catch (error) {
    console.error("處理天氣查詢時出錯:", error);
    res.status(500).json({ error: error.message || "處理查詢時發生錯誤" });
  }
});