# 第二堂:LLM API 與 Node.js 整合運用 ## 辨識圖片資訊([api](https://platform.openai.com/docs/guides/images)) ```=JavaScript /** * 處理圖片辨識查詢 * @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 ```=javascript 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](https://platform.openai.com/docs/guides/image-generation)) 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` 支援 `256x256`、`512x512`、`1024x1024`;`dall-e-3` 支援 `1024x1024`、`1792x1024`、`1024x1792`。 | | `response_format`| string | 否 | 回應格式,預設為 `url`,也可設為 `b64_json` 以獲取 base64 編碼的圖像。 | | `user` | string | 否 | 用戶唯一標識符,有助於監控和濫用檢測。 | | `quality` | string | 否 | 圖像品質,`dall-e-3` 可設為 `standard` 或 `hd`。 | | `style` | string | 否 | 圖像風格,`dall-e-3` 可設為 `vivid`(生動)或 `natural`(自然)。 | > req body > { "prompt": "一隻可愛的白色暹羅貓坐在窗台上,窗外是美麗的日落風景", "size": "1024x1024" } ```=javascript /** * 生成圖片 * @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](https://platform.openai.com/docs/guides/function-calling?api-mode=responses)、[天氣 API](https://openweathermap.org/api)) ![截圖 2025-04-07 下午5.12.08](https://hackmd.io/_uploads/SyHlwMW0yl.png) ## 環境 * 安裝 NPM:`axios` * 要取得 [weather.com](https://openweathermap.org/api) ```=javascript // 天氣 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 || "處理查詢時發生錯誤" }); } }); ```