From 3ba94c5119b6892eb45950dd1fba76875601ce6b Mon Sep 17 00:00:00 2001 From: Chris Pietschmann Date: Tue, 7 May 2024 16:11:27 -0400 Subject: [PATCH] start adding v3 api that adds a basic function --- .../api/functions/downloadHtmlToMarkdown.js | 27 ++ app/nodejs/simple/api/v3.js | 236 ++++++++++++++++++ app/nodejs/simple/app.js | 3 + app/nodejs/simple/package.json | 5 +- app/nodejs/simple/static/index.html | 2 +- 5 files changed, 271 insertions(+), 2 deletions(-) create mode 100644 app/nodejs/simple/api/functions/downloadHtmlToMarkdown.js create mode 100644 app/nodejs/simple/api/v3.js diff --git a/app/nodejs/simple/api/functions/downloadHtmlToMarkdown.js b/app/nodejs/simple/api/functions/downloadHtmlToMarkdown.js new file mode 100644 index 0000000..507d785 --- /dev/null +++ b/app/nodejs/simple/api/functions/downloadHtmlToMarkdown.js @@ -0,0 +1,27 @@ +const axios = require('axios'); +const cheerio = require('cheerio'); +const turndown = require('turndown'); + +async function downloadHtmlToMarkdown(url) { + try { + // Fetch HTML content from the URL + const response = await axios.get(url); + const html = response.data; + + // Load HTML into Cheerio + const $ = cheerio.load(html); + + // Initialize Turndown converter + const turndownService = new turndown(); + + // Convert HTML to Markdown + const markdown = turndownService.turndown($.html()); + + return markdown; + } catch (error) { + console.error('downloadHtmlToMarkdown Error:', error); + throw error; + } +} + +module.exports = downloadHtmlToMarkdown; \ No newline at end of file diff --git a/app/nodejs/simple/api/v3.js b/app/nodejs/simple/api/v3.js new file mode 100644 index 0000000..c4f456f --- /dev/null +++ b/app/nodejs/simple/api/v3.js @@ -0,0 +1,236 @@ +const { OpenAIClient, AzureKeyCredential } = require("@azure/openai"); + +const azure_openai_endpoint = process.env["AZURE_OPENAI_API_ENDPOINT"]; +const azure_openai_api_key = process.env["AZURE_OPENAI_API_KEY"]; +const azure_openai_deployment = process.env["AZURE_OPENAI_API_DEPLOYMENT_NAME"]; + +const azure_search_endpoint = process.env["AZURE_SEARCH_ENDPOINT"]; +const azure_search_key = process.env["AZURE_SEARCH_KEY"]; +const azure_search_index_name = process.env["AZURE_SEARCH_INDEX_NAME"]; + + +// Import Functions Used +const downloadHtmlToMarkdown = require('./functions/downloadHtmlToMarkdown'); + + +// Store conversation history in memory for demonstration purposes. +// In a production environment, consider storing this data in a persistent store. +let conversationHistory = []; +let rawConversationHistory = []; + +function clearConversationHistory() { + conversationHistory = [ + { role: "system", content: "" } + ]; + rawConversationHistory = []; +} +clearConversationHistory(); + +function appendConversationHistory(role, content, raw) { + conversationHistory.push({ role: role, content: content }); + rawConversationHistory.push({ role: role, content: content, raw: raw }); +} + +module.exports = (app) => { + /** + * @openapi + * /v3/chat: + * post: + * tags: + * - v3 + * description: Perform Chat Completion + * requestBody: + * required: true + * content: + * application/json: + * schema: + * type: object + * properties: + * prompt: + * type: string + * default: "" + * responses: + * 200: + * description: Returns the OpenAI chat completion response. + * responseBody: + * content: + * application/json: + * schema: + * type: object + */ + app.post(`/v3/chat`, async (req, res) => { + const prompt = req.body.prompt; + if (!prompt) { + return res.status(400).send('Prompt is required'); + } + + console.info(`\n\nUser Prompt:\n${prompt}`); + + // Add the user's prompt to the conversation history + appendConversationHistory("user", prompt); + + try { + // https://learn.microsoft.com/javascript/api/%40azure/openai/openaiclient?view=azure-node-preview + const chatClient = new OpenAIClient( + azure_openai_endpoint, + new AzureKeyCredential(azure_openai_api_key) + ); + + // const chatClient = new OpenAIClient( + // azure_openai_endpoint, + // new AzureKeyCredential(azure_openai_api_key), + // { + // apiVersion: "2024-03-01-preview" + // }); + + // https://learn.microsoft.com/javascript/api/%40azure/openai/openaiclient + const chatResponse = await chatClient.getChatCompletions( + azure_openai_deployment, // deployment name + conversationHistory, // chat request messages array + { // getCompletionsOptions - https://learn.microsoft.com/javascript/api/%40azure/openai/getchatcompletionsoptions + + // https://learn.microsoft.com/en-us/azure/ai-services/openai/reference#chat-completions + // https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/function-calling?tabs=python + // ERROR: 'Functions are not supported for this API version or this model version. To learn how to user use function calling with Azure OpenAI Service. Please refer to this wiki https://learn.microsoft.com/en-us/azure/ai-services/openai/how-to/function-calling?tabs=python' + tools: [ + { + type: "function", + function: { + name: "downloadHtmlToMarkdown", + description: "Get the contents of a web page URL", + parameters: { + type: "object", + properties: { + url: { + type: "string", + description: "The web page URL of the page to read, e.g. https://build5nines.com/category/page" + } + } + }, + required: ["url"] + } + } + ], + tool_choice: "auto", // this is the default behavior when tools are specified anyway + + /* + functions: [ + // https://learn.microsoft.com/en-us/javascript/api/%40azure/openai/functiondefinition?view=azure-node-preview + { + name: "downloadHtmlToMarkdown", + description: "Get the contents of a web page URL", + parameters: { + "url": { + type: "string", + description: "The web page URL of the page to read, e.g. https://build5nines.com/category/page" + } + } + * + parameters: [ + { + type: "object", + properties: { + url: { + type: "string", + description: "The web page URL of the page to read, e.g. https://build5nines.com/category/page" + } + } + } + ] + * + } + ], + */ + + azureExtensionOptions: { //https://learn.microsoft.com/javascript/api/%40azure/openai/getchatcompletionsoptions?view=azure-node-preview#@azure-openai-getchatcompletionsoptions-azureextensionoptions + // https://learn.microsoft.com/en-us/javascript/api/%40azure/openai/azurechatextensionconfiguration?view=azure-node-preview + extensions: [ + // https://learn.microsoft.com/en-us/javascript/api/%40azure/openai/azureextensionsoptions?view=azure-node-preview#@azure-openai-azureextensionsoptions-extensions + // https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/ai-services/openai/references/azure-search.md + { + type: 'AzureCognitiveSearch', + endpoint: azure_search_endpoint, + indexName: azure_search_index_name, + key: azure_search_key, + + // Whether queries should be restricted to use of indexed data. Default is True. + in_scope: false, + + // The query type to use with Azure Search. Default is simple + queryType: 'simple', // https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/ai-services/openai/references/azure-search.md#query-type + + // The configured strictness of the search relevance filtering. The higher of strictness, the higher of the precision but lower recall of the answer. Default is 3. + strictness: 2, // https://learn.microsoft.com/javascript/api/@azure/openai/azurecognitivesearchchatextensionconfiguration?view=azure-node-preview#@azure-openai-azurecognitivesearchchatextensionconfiguration-strictness + + // The configured top number of documents to feature for the configured query. Default is 5. + topNDocuments: 5, // https://learn.microsoft.com/javascript/api/@azure/openai/azurecognitivesearchchatextensionconfiguration?view=azure-node-preview#@azure-openai-azurecognitivesearchchatextensionconfiguration-topndocuments + + // Give the model instructions about how it should behave and any context it should reference when generating a response. You can describe the assistant's personality and tell it how to format responses. + roleInformation: // https://learn.microsoft.com/javascript/api/@azure/openai/azurecognitivesearchchatextensionconfiguration?view=azure-node-preview#@azure-openai-azurecognitivesearchchatextensionconfiguration-roleinformation + //"You are an AI assistant that helps people find information.", + "You are an expert Site Reliability Engineer (SRE) and DevOps Engineer that helps people find information." + } + // Other available options: azure_cosmos_db, AzureMachineLearning, Pinecone, Elasticsearch + // https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/ai-services/openai/references/azure-machine-learning.md + // https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/ai-services/openai/references/pinecone.md + // https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/ai-services/openai/references/elasticsearch.md + // https://github.com/MicrosoftDocs/azure-docs/blob/main/articles/ai-services/openai/references/cosmos-db.md + + ] + } + } + ); + + // Add the system's response to the conversation history + const systemResponse = chatResponse.choices[0].message.content; + appendConversationHistory("assistant", systemResponse, chatResponse); + + console.info(`\n\ntoolCalls:\n${JSON.stringify(chatResponse.choices[0].message.toolCalls)}`); + + console.info(`\n\nAI Response:\n${systemResponse}`); + + //return res.json({ response: systemResponse }); + return res.json(chatResponse); + } catch (error) { + console.error(error); + // return res.status(500).send('Error processing your request'); + return res.status(500).json(error); + } + }); + + /** + * @openapi + * /v3/history: + * get: + * tags: + * - v3 + * description: Retrieve Chat History + * responses: + * 200: + * description: Returns the OpenAI chat completion response. + * responseBody: + * content: + * application/json: + * schema: + * type: object + */ + app.get(`/v3/history`, async (req, res) => { + return res.json(rawConversationHistory); + }); + + /** + * @openapi + * /v3/clear: + * get: + * tags: + * - v3 + * description: Clear Chat History + * responses: + * 200: + * description: Returns clear chat completion response. + */ + app.get(`/v3/clear`, async (req, res) => { + clearConversationHistory(); + return res.status(200).send('Chat history cleared'); + }); +} \ No newline at end of file diff --git a/app/nodejs/simple/app.js b/app/nodejs/simple/app.js index ddcc0f6..c6ef906 100644 --- a/app/nodejs/simple/app.js +++ b/app/nodejs/simple/app.js @@ -18,9 +18,12 @@ app.use(express.static('static')) * description: Call Azure OpenAI service to make Chat Completions. * - name: v2 * description: Call Azure OpenAI service to make Chat Completions, also integrating Azure Search for additional data. +* - name: v3 +* description: Call Azure OpenAI service to make Chat Completions, also integrating Azure Search for additional data, and a custom function for reading URL content from the web. */ require('./api/v1.js')(app) require('./api/v2.js')(app) +require('./api/v3.js')(app) // Add support for Swagger UI diff --git a/app/nodejs/simple/package.json b/app/nodejs/simple/package.json index f43f3ab..d214651 100644 --- a/app/nodejs/simple/package.json +++ b/app/nodejs/simple/package.json @@ -1,14 +1,17 @@ { "dependencies": { "@azure/openai": "^1.0.0-beta.11", + "axios": "^1.6.8", "chai": "^4.4.1", "chai-http": "^4.4.0", + "cheerio": "^1.0.0-rc.12", "cors": "^2.8.5", "dotenv": "^16.4.5", "express": "^4.18.3", "mocha": "^10.3.0", "swagger-jsdoc": "^6.2.8", - "swagger-ui-express": "^5.0.0" + "swagger-ui-express": "^5.0.0", + "turndown": "^7.1.3" }, "scripts": { "test": "mocha test/*.js --exit" diff --git a/app/nodejs/simple/static/index.html b/app/nodejs/simple/static/index.html index b91ddff..c351950 100644 --- a/app/nodejs/simple/static/index.html +++ b/app/nodejs/simple/static/index.html @@ -82,7 +82,7 @@