diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d08829e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,25 @@ +version: '3.8' + +services: + app: + image: node:20.17.0 + container_name: apigpt + working_dir: /usr/src/app + volumes: + - .:/usr/src/app + ports: + - "${EXPRESS_PORT}:${EXPRESS_PORT}" + command: npm run dev + env_file: + - .env + +# mongo: +# image: mongo:latest +# container_name: mongo +# ports: +# - "27017:27017" +# volumes: +# - mongo_data:/data/db + +# volumes: +# mongo_data: diff --git a/src/helper/gpt/tool/database/aiRepository.tool.js b/src/helper/gpt/tool/database/aiRepository.tool.js index 68bb2de..38da74a 100644 --- a/src/helper/gpt/tool/database/aiRepository.tool.js +++ b/src/helper/gpt/tool/database/aiRepository.tool.js @@ -14,7 +14,7 @@ const aiRepositoryTool = { query: { type: 'object', description: - 'The query parameters used for the operation. Structure depends on the transaction type, Required for update operations. Include subpropeties query.filter and query.update', + 'The query parameters used for the operation. Structure depends on the transaction type.', properties: { filter: { type: 'object', @@ -23,8 +23,7 @@ const aiRepositoryTool = { }, update: { type: 'object', - description: - 'Data to update. Required for update operations. Include subpropeties query.filter and query.update', + description: 'Data to update (required for update transactions).', }, }, required: ['filter'], diff --git a/src/helper/swagger/path/ai/ai.swagger.js b/src/helper/swagger/path/ai/ai.swagger.js index 0e9e538..6eb908f 100644 --- a/src/helper/swagger/path/ai/ai.swagger.js +++ b/src/helper/swagger/path/ai/ai.swagger.js @@ -1,8 +1,14 @@ export default { '/ai': { post: { - summary: 'Processa a requisição AI', - description: 'Envia uma requisição para o serviço AI.', + summary: 'Interact with AI for API Commands', + description: + 'This endpoint allows users to send natural language commands to the API. You can use this to perform various actions such as creating users, fetching products, or retrieving information. Here are some examples of how to structure your commands:\n' + + '- "Create a user named John Doe with email john.doe@example.com."\n' + + '- "Find all products under $50."\n' + + '- "Show me details of the user named Jane Smith."\n' + + '- "List all users."', + tags: ['AI Command Interface'], requestBody: { required: true, content: { @@ -10,20 +16,37 @@ export default { schema: { type: 'object', properties: { - body: { - type: 'object', - }, - header: { - type: 'object', + prompt: { + type: 'string', + example: + 'Create a product named Sample Product with description "This is a sample product." and price 19.99.', }, }, + required: ['prompt'], }, }, }, }, responses: { 200: { - description: 'Resposta bem-sucedida', + description: 'Response from the API based on the provided command', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + response: { + type: 'string', + description: + 'The result of the command interpreted and executed by the API.', + }, + }, + }, + }, + }, + }, + 400: { + description: 'Invalid command, please check your input.', }, }, }, diff --git a/src/helper/swagger/path/product/product.swagger.js b/src/helper/swagger/path/product/product.swagger.js new file mode 100644 index 0000000..cb6774c --- /dev/null +++ b/src/helper/swagger/path/product/product.swagger.js @@ -0,0 +1,187 @@ +export default { + '/products': { + get: { + summary: 'Fetch All Products', + description: + 'Retrieves a list of all products with optional filtering and pagination.', + tags: ['JSON'], // Tag para agrupar na seção JSON + parameters: [ + { + name: 'priceMin', + in: 'query', + description: 'Minimum price to filter products', + required: false, + schema: { + type: 'number', + }, + }, + { + name: 'priceMax', + in: 'query', + description: 'Maximum price to filter products', + required: false, + schema: { + type: 'number', + }, + }, + { + name: 'page', + in: 'query', + description: 'Page number for pagination', + required: false, + schema: { + type: 'integer', + example: 1, + }, + }, + { + name: 'limit', + in: 'query', + description: 'Number of products per page', + required: false, + schema: { + type: 'integer', + example: 10, + }, + }, + ], + responses: { + 200: { + description: 'List of products retrieved successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + total: { + type: 'integer', + description: 'Total number of products available', + }, + products: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + }, + description: { + type: 'string', + }, + price: { + type: 'number', + }, + stock: { + type: 'number', + }, + category: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + '/product/name/{name}': { + get: { + summary: 'Fetch Specific Product by Name', + description: 'Retrieves details of a specific product by its name.', + tags: ['JSON'], // Tag para agrupar na seção JSON + parameters: [ + { + name: 'name', + in: 'path', + required: true, + description: 'Name of the product to retrieve', + schema: { + type: 'string', + }, + }, + ], + responses: { + 200: { + description: 'Product details retrieved successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + name: { + type: 'string', + }, + description: { + type: 'string', + }, + price: { + type: 'number', + }, + stock: { + type: 'number', + }, + category: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + '/product': { + post: { + summary: 'Create Product', + description: + 'Creates a new product by sending product details in JSON format.', + tags: ['JSON'], // Tag para agrupar na seção JSON + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + name: { + type: 'string', + example: 'Sample Product', + }, + description: { + type: 'string', + example: 'This is a sample product description.', + }, + price: { + type: 'number', + example: 29.99, + }, + stock: { + type: 'number', + example: 100, + }, + category: { + type: 'string', + example: 'Electronics', + }, + }, + required: ['name', 'description', 'price', 'category'], + }, + }, + }, + }, + responses: { + 201: { + description: 'Product created successfully', + }, + 400: { + description: 'Invalid input', + }, + }, + }, + }, +}; diff --git a/src/helper/swagger/path/product/productNatural.swagger.js b/src/helper/swagger/path/product/productNatural.swagger.js new file mode 100644 index 0000000..4d6bd94 --- /dev/null +++ b/src/helper/swagger/path/product/productNatural.swagger.js @@ -0,0 +1,146 @@ +export default { + '/products/natural': { + get: { + summary: 'Fetch All Products via Natural Language', + description: + 'Retrieves a list of all products by interpreting natural language input with optional filters.', + tags: ['Natural Language'], // Tag para agrupar na seção Natural Language + parameters: [ + { + name: 'prompt', + in: 'query', + required: true, + description: 'Natural language query to retrieve products', + schema: { + type: 'string', + example: 'Show me all products priced between 10 and 50', + }, + }, + ], + responses: { + 200: { + description: 'List of products retrieved successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + total: { + type: 'integer', + description: 'Total number of products available', + }, + products: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + }, + description: { + type: 'string', + }, + price: { + type: 'number', + }, + stock: { + type: 'number', + }, + category: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + '/product/natural': { + get: { + summary: 'Fetch Specific Product via Natural Language', + description: + 'Retrieves details of a specific product by interpreting natural language input.', + tags: ['Natural Language'], // Tag para agrupar na seção Natural Language + parameters: [ + { + name: 'prompt', + in: 'query', + required: true, + description: 'Natural language query to retrieve a specific product', + schema: { + type: 'string', + example: 'Find the product named Sample Product.', + }, + }, + ], + responses: { + 200: { + description: 'Product details retrieved successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + name: { + type: 'string', + }, + description: { + type: 'string', + }, + price: { + type: 'number', + }, + stock: { + type: 'number', + }, + category: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + '/product/natural/create': { + post: { + summary: 'Create Product via Natural Language', + description: + 'Creates a new product by interpreting natural language input.', + tags: ['Natural Language'], // Tag para agrupar na seção Natural Language + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + input: { + type: 'string', + example: + 'I want to create a product named Sample Product, with description "This is a sample product description.", price 29.99, stock 100, and category Electronics.', + }, + }, + required: ['input'], + }, + }, + }, + }, + responses: { + 201: { + description: 'Product created successfully', + }, + 400: { + description: 'Invalid input', + }, + }, + }, + }, +}; diff --git a/src/helper/swagger/path/user/user.swagger.js b/src/helper/swagger/path/user/user.swagger.js new file mode 100644 index 0000000..23b326e --- /dev/null +++ b/src/helper/swagger/path/user/user.swagger.js @@ -0,0 +1,130 @@ +export default { + '/users': { + get: { + summary: 'Fetch All Users', + description: 'Retrieves a list of all users.', + tags: ['JSON'], + responses: { + 200: { + description: 'List of users retrieved successfully', + content: { + 'application/json': { + schema: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + }, + email: { + type: 'string', + }, + birthDate: { + type: 'string', + }, + mobile: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + '/user/name/{name}': { + get: { + summary: 'Fetch Specific User by Name', + description: 'Retrieves details of a specific user by their name.', + tags: ['JSON'], + parameters: [ + { + name: 'name', + in: 'path', + required: true, + description: 'Name of the user to retrieve', + schema: { + type: 'string', + }, + }, + ], + responses: { + 200: { + description: 'User details retrieved successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + name: { + type: 'string', + }, + email: { + type: 'string', + }, + birthDate: { + type: 'string', + }, + mobile: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + '/user': { + post: { + summary: 'Create User', + description: 'Creates a new user by sending user details in JSON format.', + tags: ['JSON'], + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + name: { + type: 'string', + example: 'John Doe', + }, + email: { + type: 'string', + example: 'john.doe@example.com', + }, + password: { + type: 'string', + example: 'securepassword123', + }, + birthDate: { + type: 'string', + example: '1990-01-01', + }, + mobile: { + type: 'string', + example: '+1234567890', + }, + }, + required: ['name', 'email', 'password', 'birthDate', 'mobile'], + }, + }, + }, + }, + responses: { + 201: { + description: 'User created successfully', + }, + 400: { + description: 'Invalid input', + }, + }, + }, + }, +}; diff --git a/src/helper/swagger/path/user/userNatural.swagger.js b/src/helper/swagger/path/user/userNatural.swagger.js new file mode 100644 index 0000000..0a1a3aa --- /dev/null +++ b/src/helper/swagger/path/user/userNatural.swagger.js @@ -0,0 +1,139 @@ +export default { + '/users/natural': { + get: { + summary: 'Fetch All Users via Natural Language', + description: + 'Retrieves a list of all users by interpreting natural language input with optional filters.', + tags: ['Natural Language'], // Tag para agrupar na seção Natural Language + parameters: [ + { + name: 'input', + in: 'query', + required: true, + description: 'Natural language query to retrieve users', + schema: { + type: 'string', + example: 'Show me all users.', + }, + }, + ], + responses: { + 200: { + description: 'List of users retrieved successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + total: { + type: 'integer', + description: 'Total number of users available', + }, + users: { + type: 'array', + items: { + type: 'object', + properties: { + name: { + type: 'string', + }, + email: { + type: 'string', + }, + birthDate: { + type: 'string', + }, + mobile: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + '/user/natural': { + get: { + summary: 'Fetch Specific User via Natural Language', + description: + 'Retrieves details of a specific user by interpreting natural language input.', + tags: ['Natural Language'], // Tag para agrupar na seção Natural Language + parameters: [ + { + name: 'input', + in: 'query', + required: true, + description: 'Natural language query to retrieve a specific user', + schema: { + type: 'string', + example: 'Find the user named John Doe.', + }, + }, + ], + responses: { + 200: { + description: 'User details retrieved successfully', + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + name: { + type: 'string', + }, + email: { + type: 'string', + }, + birthDate: { + type: 'string', + }, + mobile: { + type: 'string', + }, + }, + }, + }, + }, + }, + }, + }, + }, + '/user/natural/create': { + post: { + summary: 'Create User via Natural Language', + description: 'Creates a new user by interpreting natural language input.', + tags: ['Natural Language'], // Tag para agrupar na seção Natural Language + requestBody: { + required: true, + content: { + 'application/json': { + schema: { + type: 'object', + properties: { + input: { + type: 'string', + example: + 'I want to create a user named John Doe, with email john.doe@example.com, password securepassword123, birth date 1990-01-01, and mobile +1234567890.', + }, + }, + required: ['input'], + }, + }, + }, + }, + responses: { + 201: { + description: 'User created successfully', + }, + 400: { + description: 'Invalid input', + }, + }, + }, + }, +}; diff --git a/src/helper/swagger/swagger.config.js b/src/helper/swagger/swagger.config.js index 225b27d..f3d7492 100644 --- a/src/helper/swagger/swagger.config.js +++ b/src/helper/swagger/swagger.config.js @@ -1,18 +1,25 @@ import swaggerJsDoc from 'swagger-jsdoc'; import swaggerUi from 'swagger-ui-express'; -import aiPath from './path/ai/ai.swagger.js'; - +import * as dotenv from 'dotenv'; +import userSwagger from './path/user/user.swagger.js'; +import userNaturalSwagger from './path/user/userNatural.swagger.js'; +import productSwagger from './path/product/product.swagger.js'; +import productNaturalSwagger from './path/product/productNatural.swagger.js'; +import aiSwagger from './path/ai/ai.swagger.js'; +dotenv.config(); const swaggerOptions = { swaggerDefinition: { openapi: '3.0.0', info: { - title: 'API Documentação', + title: 'GPT Integration API', version: '1.0.0', - description: 'Documentação da API usando Swagger', + description: `This API serves as a direct integration with GPT, showcasing the power of GPT combined with various tools and how it behaves in response to different requests. All routes described in this Swagger documentation are fictional. Every request sent to the API is processed by middleware that identifies the method used, the request body, and the headers. It then utilizes its tools to determine the appropriate actions to take with the request. + +Notably, the API is capable of handling both standard JSON calls and natural language inputs, such as “I want to create a user. My name is John Doe, my phone number is xxx-xxx-xxxx,” among others.`, }, servers: [ { - url: `http://localhost:${process.env.EXPRESS_PORT}`, + url: process.env.SWAGGER_URL, }, ], }, @@ -23,7 +30,11 @@ const swaggerDocs = swaggerJsDoc(swaggerOptions); swaggerDocs.paths = { ...swaggerDocs.paths, - ...aiPath, + ...userSwagger, + ...userNaturalSwagger, + ...productSwagger, + ...productNaturalSwagger, + ...aiSwagger, }; const setupSwagger = app => { diff --git a/src/main.js b/src/main.js index cb9c890..ed195d7 100644 --- a/src/main.js +++ b/src/main.js @@ -14,12 +14,10 @@ const start = async () => { const app = express(); app.use(express.json()); + setupSwagger(app); app.use(asyncHandler(validateRequest)); app.use(asyncHandler(gptMiddleware)); app.use(errorHandler); - - setupSwagger(app); - app.listen(process.env.EXPRESS_PORT || 3000, () => { console.log(`Server running on port ${process.env.EXPRESS_PORT || 3000}`); }); diff --git a/src/service/ai/ai.service.js b/src/service/ai/ai.service.js index b986786..00fb76a 100644 --- a/src/service/ai/ai.service.js +++ b/src/service/ai/ai.service.js @@ -25,42 +25,41 @@ const aiService = async ({ let response = await gpt({ completion: context }); async function callTool(gpt) { - const { name, arguments: args } = gpt.tool_calls[0].function; - let functionResponse; + for (const toolCall of gpt.tool_calls) { + const { name, arguments: args } = toolCall.function; + let functionResponse; - if (!functions[name]) { - return { - message: 'Function not found', - statusCode: 404, - status: 'error', - }; // Retorna imediatamente - } else { - functionResponse = await functions[name](JSON.parse(args)); - console.log(functionResponse); - } + if (!functions[name]) { + return { + message: 'Function not found', + statusCode: 404, + status: 'error', + }; + } else { + functionResponse = await functions[name](JSON.parse(args)); + console.log(functionResponse); + } - context.push({ - role: 'tool', - content: JSON.stringify(functionResponse), - tool_call_id: gpt.tool_calls[0].id, - }); - return null; // Retorna null se não houver erro + context.push({ + role: 'tool', + content: JSON.stringify(functionResponse) || 'Return error', + tool_call_id: toolCall.id, + }); + } } context.push(response); let retries = 0; while (!response.content && retries < 20) { - const toolErrorResponse = await callTool(response); // Captura a resposta da função - if (toolErrorResponse) { - return toolErrorResponse; // Se houve erro, retorna imediatamente - } + console.log(context); + await callTool(response); response = await gpt({ completion: context }); context.push(response); retries++; } - + console.log(context); return response; } catch (err) { return err.message;