From 2b17bc08a4e402542a7b714349899fbd450af726 Mon Sep 17 00:00:00 2001 From: fatwang2 Date: Wed, 24 Apr 2024 03:04:11 +0800 Subject: [PATCH] support groq --- README-EN.md | 5 +- README.md | 6 +- search2groq.js | 695 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 702 insertions(+), 4 deletions(-) create mode 100644 search2groq.js diff --git a/README-EN.md b/README-EN.md index 083b581..22a5c41 100644 --- a/README-EN.md +++ b/README-EN.md @@ -40,6 +40,7 @@ Help your LLM API support networking, search, news, web page summarization, has | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `OpenAI` | search, news, crawler | stream, unstream| Zeabur, Local deployment, Cloudflare Worker, Vercel| | `Azure OpenAI` | search, news, crawler | stream, unstream| Cloudflare Worker| +| `Groq` | search, news, crawler | stream, unstream| Cloudflare Worker| | `Gemini` | search | stream, unstream| Cloudflare Worker| | `Moonshot` | search, news, crawler | unstream| Zeabur, Local deployment, Cloudflare Worker, Vercel| @@ -81,7 +82,7 @@ http://localhost:3014/v1/chat/completions ``` **Cloudflare worker** -1. Copy the code of [search2openai.js](search2openai.js), or [search2gemini.js](search2gemini.js),no modifications needed! Deploy in cloudflare's worker, after going online, the worker's address can be used as your interface call's custom domain address, note the concatenation, worker address only represents the part before v1 +1. Copy the code of [search2openai.js](search2openai.js), or [search2gemini.js](search2gemini.js), or [search2groq.js](search2groq.js), no modifications needed! Deploy in cloudflare's worker, after going online, the worker's address can be used as your interface call's custom domain address, note the concatenation, worker address only represents the part before v1 2. Configure variables in the worker(only openai) ![Effect Example](pictures/worker.png) @@ -105,7 +106,7 @@ This project provides some additional configuration options, which can be set th | Environment Variable | Required | Description | Example | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `SEARCH_SERVICE` | Yes | Your search service. The key of the service you choose needs to be configured. Supports search1api, google, bing, serpapi, serper, duckduckgo. | `search1api, google, bing, serpapi, serper, duckduckgo` | -| `APIBASE` | No | OpenAI third-party proxy address. If using Moonshot, fill in `https://api.moonshot.cn`. | `https://api.openai.com` | +| `APIBASE` | No | Third-party proxy address. | `https://api.openai.com, https://api.moonshot.cn, https://api.groq.com/openai` | | `MAX_RESULTS` | No | Number of search results. | `10` | | `CRAWL_RESULTS` | No | The number of deep searches (retrieve the main text of the webpage after searching). Currently only supports search1api, deep search will be slow. | `1` | | `SEARCH1API_KEY` | Conditional | Required if search1api is selected. My own fast and cheap search service. Apply at https://search21api.com. | `xxx` | diff --git a/README.md b/README.md index c073fe5..ee40367 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Buy Me A Coffee # 版本更新 +- V0.2.4,20240424,支持 Groq 的llama-3、mistral等模型,速度起飞 - V0.2.3,20240423,Cloudflare Worker版本支持Azure OpenAI;支持授权码,可自定义用户的请求key - V0.2.2,20240420,支持Moonshot的非流式模式 - V0.2.1,20240310,支持Google、Bing、Duckduckgo、Search1API新闻类搜索;支持通过环境变量MAX_RESULTS调整搜索结果数量;支持通过环境变量CRAWL_RESULTS调整希望深度搜索的数量 @@ -34,6 +35,7 @@ | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `OpenAI` | 联网、新闻、内容爬取 | 流式、非流式| Zeabur、本地部署、Cloudflare Worker、Vercel| | `Azure OpenAI` | 联网、新闻、内容爬取 | 流式、非流式| Cloudflare Worker| +| `Groq` | 联网、新闻、内容爬取 | 流式、非流式| Cloudflare Worker| | `Gemini` | 联网 | 流式、非流式| Cloudflare Worker| | `Moonshot` | 联网、新闻、内容爬取 | 非流式| Zeabur、本地部署、Cloudflare Worker、Vercel| @@ -75,7 +77,7 @@ http://localhost:3014/v1/chat/completions ``` **Cloudflare Worker部署** -1. 复制[search2openai.js](search2openai.js)或者[search2gemini.js](search2gemini.js)的代码,不需要任何修改!在cloudflare的worker里部署,上线后的worker的地址可作为你接口调用时的自定义域名地址,注意拼接,worker地址仅代表v1前的部分 +1. 复制[search2openai.js](search2openai.js)或者[search2gemini.js](search2gemini.js)或者[search2groq.js](search2groq.js)的代码,不需要任何修改!在cloudflare的worker里部署,上线后的worker的地址可作为你接口调用时的自定义域名地址,注意拼接,worker地址仅代表v1前的部分 2. 在worker中配置环境变量 ![效果示例](pictures/worker.png) @@ -99,7 +101,7 @@ http://localhost:3014/v1/chat/completions | 环境变量 | 是否必须 | 描述 | 例子 | | -------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- | | `SEARCH_SERVICE` | Yes | 你的搜索服务,选择什么服务,就需要配置什么服务的key支持search1api、google、bing、serpapi、serper、duckduckgo| `search1api, google, bing, serpapi, serper, duckduckgo`| -| `APIBASE` | No | OpenAI 三方代理地址,使用Moonshot则填写`https://api.moonshot.cn`| `https://api.openai.com`| +| `APIBASE` | No | 三方代理地址`| `https://api.openai.com, https://api.moonshot.cn, https://api.groq.com/openai`| | `MAX_RESULTS` | No | 搜索结果条数| `10`| | `CRAWL_RESULTS` | No | 要进行深度搜索(搜索后获取网页正文)的数量,目前仅支持 search1api,深度速度会慢| `1`| | `SEARCH1API_KEY` | No | 如选search1api必填,我自己搭建的搜索服务,又快又便宜,申请地址 https://search21api.com| `xxx`| diff --git a/search2groq.js b/search2groq.js new file mode 100644 index 0000000..c8f4c3d --- /dev/null +++ b/search2groq.js @@ -0,0 +1,695 @@ +(() => { + // openai.js + var corsHeaders = { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "GET, POST, OPTIONS", + // 允许的HTTP方法 + "Access-Control-Allow-Headers": + "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization", + "Access-Control-Max-Age": "86400", + // 预检请求结果的缓存时间 + }; + + var header_auth = "Authorization"; //azure use "api-key" + var header_auth_val = "Bearer "; + + // get variables from env + const api_type = typeof OPENAI_TYPE !== "undefined" ? OPENAI_TYPE : "openai"; + const apiBase = + typeof APIBASE !== "undefined" ? APIBASE : "https://api.openai.com"; + const resource_name = + typeof RESOURCE_NAME !== "undefined" ? RESOURCE_NAME : "xxxxx"; + const deployName = + typeof DEPLOY_NAME !== "undefined" ? DEPLOY_NAME : "gpt-35-turbo"; + const api_ver = + typeof API_VERSION !== "undefined" ? API_VERSION : "2024-03-01-preview"; + let openai_key = typeof OPENAI_API_KEY !== "undefined" ? OPENAI_API_KEY : ""; + const azure_key = typeof AZURE_API_KEY !== "undefined" ? AZURE_API_KEY : ""; + const auth_keys = typeof AUTH_KEYS !== "undefined" ? AUTH_KEYS : [""]; + + let fetchAPI = ""; + let request_header = new Headers({ + "Content-Type": "application/json", + Authorization: "", + "api-key": "", + }); + + addEventListener("fetch", (event) => { + console.log( + `\u6536\u5230\u8BF7\u6C42: ${event.request.method} ${event.request.url}` + ); + const url = new URL(event.request.url); + if (event.request.method === "OPTIONS") { + return event.respondWith(handleOptions()); + } + + const authHeader = event.request.headers.get("Authorization"); + let apiKey = ""; + if (authHeader) { + apiKey = authHeader.split(" ")[1]; + if (!auth_keys.includes(apiKey) || !openai_key) { + openai_key = apiKey; + } + } else { + return event.respondWith( + new Response("Authorization header is missing", { + status: 400, + headers: corsHeaders, + }) + ); + } + + if (api_type === "azure") { + fetchAPI = `https://${resource_name}.openai.azure.com/openai/deployments/${deployName}/chat/completions?api-version=${api_ver}`; + header_auth = "api-key"; + header_auth_val = ""; + apiKey = azure_key; + } else { + //openai + fetchAPI = `${apiBase}/v1/chat/completions`; + header_auth = "Authorization"; + header_auth_val = "Bearer "; + apiKey = openai_key; + } + + if (url.pathname === "/v1/chat/completions") { + //openai-style request + console.log("接收到 fetch 事件"); + event.respondWith(handleRequest(event.request, fetchAPI, apiKey)); + } else { + //other request + event.respondWith( + handleOtherRequest(apiBase, apiKey, event.request, url.pathname).then( + (response) => { + return new Response(response.body, { + status: response.status, + headers: { ...response.headers, ...corsHeaders }, + }); + } + ) + ); + } + }); + function handleOptions() { + return new Response(null, { + status: 204, + headers: corsHeaders, + }); + } + async function handleOtherRequest(apiBase, apiKey, request, pathname) { + const headers = new Headers(request.headers); + headers.delete("Host"); + if (api_type === "azure") { + headers.set("api-key", `${apiKey}`); + } else { + headers.set("Authorization", `Bearer ${apiKey}`); + } + + const response = await fetch(`${apiBase}${pathname}`, { + method: request.method, + headers, + body: request.body, + }); + let data; + if (pathname.startsWith("/v1/audio/")) { + data = await response.arrayBuffer(); + return new Response(data, { + status: response.status, + headers: { "Content-Type": "audio/mpeg", ...corsHeaders }, + }); + } else { + data = await response.json(); + return new Response(JSON.stringify(data), { + status: response.status, + headers: corsHeaders, + }); + } + } + async function search(query) { + console.log(`正在使用查询进行自定义搜索: ${JSON.stringify(query)}`); + try { + const response = await fetch("https://search.search2ai.one", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: + typeof SEARCH1API_KEY !== "undefined" + ? `Bearer ${SEARCH1API_KEY}` + : "", + google_cx: typeof GOOGLE_CX !== "undefined" ? GOOGLE_CX : "", + google_key: typeof GOOGLE_KEY !== "undefined" ? GOOGLE_KEY : "", + serpapi_key: typeof SERPAPI_KEY !== "undefined" ? SERPAPI_KEY : "", + serper_key: typeof SERPER_KEY !== "undefined" ? SERPER_KEY : "", + bing_key: typeof BING_KEY !== "undefined" ? BING_KEY : "", + apibase: + typeof APIBASE !== "undefined" ? APIBASE : "https://api.openai.com", + }, + body: JSON.stringify({ + query, + search_service: SEARCH_SERVICE, + max_results: typeof MAX_RESULTS !== "undefined" ? MAX_RESULTS : "5", + crawl_results: + typeof CRAWL_RESULTS !== "undefined" ? MAX_RESULTS : "0", + }), + }); + if (!response.ok) { + console.error(`API 请求失败, 状态码: ${response.status}`); + return `API 请求失败, 状态码: ${response.status}`; + } + const contentType = response.headers.get("content-type"); + if (!contentType || !contentType.includes("application/json")) { + console.error("收到的响应不是有效的 JSON 格式"); + return "收到的响应不是有效的 JSON 格式"; + } + const data = await response.json(); + console.log("自定义搜索服务调用完成"); + return JSON.stringify(data); + } catch (error) { + console.error(`在 search 函数中捕获到错误: ${error}`); + return `在 search 函数中捕获到错误: ${error}`; + } + } + async function news(query) { + console.log(`正在使用查询进行新闻搜索: ${JSON.stringify(query)}`); + try { + const response = await fetch("https://search.search2ai.one/news", { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: + typeof SEARCH1API_KEY !== "undefined" + ? `Bearer ${SEARCH1API_KEY}` + : "", + google_cx: typeof GOOGLE_CX !== "undefined" ? GOOGLE_CX : "", + google_key: typeof GOOGLE_KEY !== "undefined" ? GOOGLE_KEY : "", + serpapi_key: typeof SERPAPI_KEY !== "undefined" ? SERPAPI_KEY : "", + serper_key: typeof SERPER_KEY !== "undefined" ? SERPER_KEY : "", + bing_key: typeof BING_KEY !== "undefined" ? BING_KEY : "", + apibase: + typeof APIBASE !== "undefined" ? APIBASE : "https://api.openai.com", + }, + body: JSON.stringify({ + query, + search_service: SEARCH_SERVICE, + max_results: typeof MAX_RESULTS !== "undefined" ? MAX_RESULTS : "10", + crawl_results: + typeof CRAWL_RESULTS !== "undefined" ? MAX_RESULTS : "0", + }), + }); + if (!response.ok) { + console.error(`API 请求失败, 状态码: ${response.status}`); + return `API 请求失败, 状态码: ${response.status}`; + } + const contentType = response.headers.get("content-type"); + if (!contentType || !contentType.includes("application/json")) { + console.error("收到的响应不是有效的 JSON 格式"); + return "收到的响应不是有效的 JSON 格式"; + } + const data = await response.json(); + console.log("新闻搜索服务调用完成"); + return JSON.stringify(data); + } catch (error) { + console.error( + `\u5728 news \u51FD\u6570\u4E2D\u6355\u83B7\u5230\u9519\u8BEF: ${error}` + ); + return `\u5728 news \u51FD\u6570\u4E2D\u6355\u83B7\u5230\u9519\u8BEF: ${error}`; + } + } + async function crawler(url) { + console.log( + `\u6B63\u5728\u4F7F\u7528 URL \u8FDB\u884C\u81EA\u5B9A\u4E49\u722C\u53D6:${JSON.stringify( + url + )}` + ); + try { + const response = await fetch("https://crawler.search2ai.one", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + url, + }), + }); + if (!response.ok) { + console.error( + `API \u8BF7\u6C42\u5931\u8D25, \u72B6\u6001\u7801: ${response.status}` + ); + return `API \u8BF7\u6C42\u5931\u8D25, \u72B6\u6001\u7801: ${response.status}`; + } + const contentType = response.headers.get("content-type"); + if (!contentType || !contentType.includes("application/json")) { + console.error( + "\u6536\u5230\u7684\u54CD\u5E94\u4E0D\u662F\u6709\u6548\u7684 JSON \u683C\u5F0F" + ); + return "\u6536\u5230\u7684\u54CD\u5E94\u4E0D\u662F\u6709\u6548\u7684 JSON \u683C\u5F0F"; + } + const data = await response.json(); + console.log( + "\u81EA\u5B9A\u4E49\u722C\u53D6\u670D\u52A1\u8C03\u7528\u5B8C\u6210" + ); + return JSON.stringify(data); + } catch (error) { + console.error( + `\u5728 crawl \u51FD\u6570\u4E2D\u6355\u83B7\u5230\u9519\u8BEF: ${error}` + ); + return `\u5728 crawler \u51FD\u6570\u4E2D\u6355\u83B7\u5230\u9519\u8BEF: ${error}`; + } + } + async function handleRequest(request, fetchAPI, apiKey) { + console.log( + `\u5F00\u59CB\u5904\u7406\u8BF7\u6C42: ${request.method} ${request.url}` + ); + if (request.method !== "POST") { + console.log( + `\u4E0D\u652F\u6301\u7684\u8BF7\u6C42\u65B9\u6CD5: ${request.method}` + ); + return new Response("Method Not Allowed", { + status: 405, + headers: corsHeaders, + }); + } + + const requestData = await request.json(); + console.log("\u8BF7\u6C42\u6570\u636E:", requestData); + const stream = requestData.stream || false; + const userMessages = requestData.messages.filter( + (message) => message.role === "user" + ); + const latestUserMessage = userMessages[userMessages.length - 1]; + const model = requestData.model; + const isContentArray = Array.isArray(latestUserMessage.content); + const defaultMaxTokens = 3e3; + const maxTokens = requestData.max_tokens || defaultMaxTokens; + const body = JSON.stringify({ + model, + messages: requestData.messages, + max_tokens: maxTokens, + ...(isContentArray + ? {} + : { + tools: [ + { + type: "function", + function: { + name: "search", + description: "search for factors and weathers", + parameters: { + type: "object", + properties: { + query: { + type: "string", + description: "The query to search.", + }, + }, + required: ["query"], + }, + }, + }, + { + type: "function", + function: { + name: "news", + description: "Search for news", + parameters: { + type: "object", + properties: { + query: { + type: "string", + description: "The query to search for news.", + }, + }, + required: ["query"], + }, + }, + }, + { + type: "function", + function: { + name: "crawler", + description: "Get the content of a specified url", + parameters: { + type: "object", + properties: { + url: { + type: "string", + description: "The URL of the webpage", + }, + }, + required: ["url"], + }, + }, + }, + ], + tool_choice: "auto", + }), + }); + + request_header.set(`${header_auth}`, `${header_auth_val}${apiKey}`); + + if (stream) { + const openAIResponse = await fetch(fetchAPI, { + method: "POST", + headers: request_header, + body, + }); + if (openAIResponse.status !== 200) { + console.error( + `OpenAI API \u8BF7\u6C42\u5931\u8D25,\u72B6\u6001\u7801: ${openAIResponse.status}` + ); + return new Response( + `OpenAI API \u8BF7\u6C42\u5931\u8D25,\u72B6\u6001\u7801: ${openAIResponse.status}`, + { + status: 500, + headers: corsHeaders, + } + ); + } + const data = await openAIResponse.json(); + console.log( + "OpenAI API \u54CD\u5E94\u72B6\u6001\u7801:", + openAIResponse.status + ); + if (!data.choices || data.choices.length === 0) { + console.log("\u6570\u636E\u4E2D\u6CA1\u6709\u9009\u62E9\u9879"); + return new Response( + "\u6570\u636E\u4E2D\u6CA1\u6709\u9009\u62E9\u9879", + { status: 500 } + ); + } + console.log( + "OpenAI API \u54CD\u5E94\u63A5\u6536\u5B8C\u6210\uFF0C\u68C0\u67E5\u662F\u5426\u9700\u8981\u8C03\u7528\u81EA\u5B9A\u4E49\u51FD\u6570" + ); + let messages = requestData.messages; + messages.push(data.choices[0].message); + let calledCustomFunction = false; + if (data.choices[0].message.tool_calls) { + const toolCalls = data.choices[0].message.tool_calls; + const availableFunctions = { + search: search, + news: news, + crawler: crawler, + }; + for (const toolCall of toolCalls) { + const functionName = toolCall.function.name; + const functionToCall = availableFunctions[functionName]; + const functionArgs = JSON.parse(toolCall.function.arguments); + let functionResponse; + if (functionName === "search") { + functionResponse = await functionToCall(functionArgs.query); + } else if (functionName === "crawler") { + functionResponse = await functionToCall(functionArgs.url); + } else if (functionName === "news") { + functionResponse = await functionToCall(functionArgs.query); + } + messages.push({ + tool_call_id: toolCall.id, + role: "tool", + name: functionName, + content: functionResponse, + }); + if ( + functionName === "search" || + functionName === "crawler" || + functionName === "news" + ) { + calledCustomFunction = true; + } + } + } + if (calledCustomFunction) { + console.log( + "\u51C6\u5907\u53D1\u9001\u7B2C\u4E8C\u6B21 OpenAI API \u8BF7\u6C42" + ); + console.log( + "Messages before sending second request:", + JSON.stringify(messages, null, 2) + ); + + const secondRequestBody = JSON.stringify({ + model, + messages, + }); + + console.log("Second request body:", secondRequestBody); + const secondResponse = await fetch(fetchAPI, { + method: "POST", + headers: request_header, + body: secondRequestBody, + }); + + console.log("Second response status:", secondResponse.status); + console.log("Second response headers:", secondResponse.headers); + if (secondResponse.status !== 200) { + throw new Error( + `OpenAI API 第二次请求失败,状态码: ${secondResponse.status}` + ); + } + + const data = await secondResponse.json(); + const content = data.choices[0].message.content; + const words = content.split(/(\s+)/); + + const stream = new ReadableStream({ + async start(controller) { + const baseData = { + id: data.id, + object: "chat.completion.chunk", + created: data.created, + model: data.model, + system_fingerprint: data.system_fingerprint, + choices: [ + { + index: 0, + delta: {}, + logprobs: null, + finish_reason: null, + }, + ], + x_groq: { + id: data.x_groq ? data.x_groq.id : null, + }, + }; + + for (const word of words) { + const chunkData = { + ...baseData, + choices: [ + { + ...baseData.choices[0], + delta: { content: word.includes("\n") ? word : word + " " }, + }, + ], + }; + const sseMessage = `data: ${JSON.stringify(chunkData)}\n\n`; + controller.enqueue(new TextEncoder().encode(sseMessage)); + await new Promise((resolve) => setTimeout(resolve, 5)); + } + + const finalChunkData = { + ...baseData, + choices: [ + { + ...baseData.choices[0], + finish_reason: data.choices[0].finish_reason, + }, + ], + x_groq: { + ...baseData.x_groq, + usage: data.usage, + }, + }; + const finalSseMessage = `data: ${JSON.stringify( + finalChunkData + )}\n\ndata: [DONE]\n\n`; + controller.enqueue(new TextEncoder().encode(finalSseMessage)); + controller.close(); + }, + }); + + return new Response(stream, { + status: 200, + headers: { + "Content-Type": "text/event-stream", + ...corsHeaders, + }, + }); + } else { + const content = data.choices[0].message.content; + const words = content.split(/(\s+)/); + + const stream = new ReadableStream({ + async start(controller) { + const baseData = { + id: data.id, + object: "chat.completion.chunk", + created: data.created, + model: data.model, + system_fingerprint: data.system_fingerprint, + choices: [ + { + index: 0, + delta: {}, + logprobs: null, + finish_reason: null, + }, + ], + x_groq: { + id: data.x_groq ? data.x_groq.id : null, + }, + }; + + for (const word of words) { + const chunkData = { + ...baseData, + choices: [ + { + ...baseData.choices[0], + delta: { content: word.includes("\n") ? word : word + " " }, + }, + ], + }; + const sseMessage = `data: ${JSON.stringify(chunkData)}\n\n`; + controller.enqueue(new TextEncoder().encode(sseMessage)); + await new Promise((resolve) => setTimeout(resolve, 5)); + } + + const finalChunkData = { + ...baseData, + choices: [ + { + ...baseData.choices[0], + finish_reason: data.choices[0].finish_reason, + }, + ], + x_groq: { + ...baseData.x_groq, + usage: data.usage, + }, + }; + const finalSseMessage = `data: ${JSON.stringify( + finalChunkData + )}\n\ndata: [DONE]\n\n`; + controller.enqueue(new TextEncoder().encode(finalSseMessage)); + controller.close(); + }, + }); + + return new Response(stream, { + status: 200, + headers: { + "Content-Type": "text/event-stream", + ...corsHeaders, + }, + }); + } + } else { + const openAIResponse = await fetch(fetchAPI, { + method: "POST", + headers: request_header, + body, + }); + if (openAIResponse.status !== 200) { + console.error( + `OpenAI API \u8BF7\u6C42\u5931\u8D25,\u72B6\u6001\u7801: ${openAIResponse.status}` + ); + return new Response( + `OpenAI API \u8BF7\u6C42\u5931\u8D25,\u72B6\u6001\u7801: ${openAIResponse.status}`, + { + status: 500, + headers: corsHeaders, + } + ); + } + const data = await openAIResponse.json(); + console.log( + "OpenAI API \u54CD\u5E94\u72B6\u6001\u7801:", + openAIResponse.status + ); + if (!data.choices || data.choices.length === 0) { + console.log("\u6570\u636E\u4E2D\u6CA1\u6709\u9009\u62E9\u9879"); + return new Response( + "\u6570\u636E\u4E2D\u6CA1\u6709\u9009\u62E9\u9879", + { status: 500 } + ); + } + console.log( + "OpenAI API \u54CD\u5E94\u63A5\u6536\u5B8C\u6210\uFF0C\u68C0\u67E5\u662F\u5426\u9700\u8981\u8C03\u7528\u81EA\u5B9A\u4E49\u51FD\u6570" + ); + let messages = requestData.messages; + messages.push(data.choices[0].message); + let calledCustomFunction = false; + if (data.choices[0].message.tool_calls) { + const toolCalls = data.choices[0].message.tool_calls; + const availableFunctions = { + search: search, + news: news, + crawler: crawler, + }; + for (const toolCall of toolCalls) { + const functionName = toolCall.function.name; + const functionToCall = availableFunctions[functionName]; + const functionArgs = JSON.parse(toolCall.function.arguments); + let functionResponse; + if (functionName === "search") { + functionResponse = await functionToCall(functionArgs.query); + } else if (functionName === "crawler") { + functionResponse = await functionToCall(functionArgs.url); + } else if (functionName === "news") { + functionResponse = await functionToCall(functionArgs.query); + } + messages.push({ + tool_call_id: toolCall.id, + role: "tool", + name: functionName, + content: functionResponse, + }); + if ( + functionName === "search" || + functionName === "crawler" || + functionName === "news" + ) { + calledCustomFunction = true; + } + } + } + if (calledCustomFunction) { + console.log( + "\u51C6\u5907\u53D1\u9001\u7B2C\u4E8C\u6B21 OpenAI API \u8BF7\u6C42" + ); + console.log( + "Messages before sending second request:", + JSON.stringify(messages, null, 2) + ); + const requestBody = { + model, + messages, + }; + const secondResponse = await fetch(fetchAPI, { + method: "POST", + headers: request_header, + body: JSON.stringify(requestBody), + }); + console.log("\u54CD\u5E94\u72B6\u6001\u7801: 200"); + const data2 = await secondResponse.json(); + return new Response(JSON.stringify(data2), { + status: 200, + headers: { + "Content-Type": "application/json", + ...corsHeaders, + }, + }); + } else { + console.log("\u54CD\u5E94\u72B6\u6001\u7801: 200"); + return new Response(JSON.stringify(data), { + status: 200, + headers: { + "Content-Type": "application/json", + ...corsHeaders, + }, + }); + } + } + } +})(); +//# sourceMappingURL=openai.js.map