Criei um Chatbot para me Ajudar a Economizar na Compra de Materiais de Construção

Por Gaspar Barancelli Junior em 23 de janeiro de 2025
Imagem ilustrativa sobre o post Criei um Chatbot para me Ajudar a Economizar na Compra de Materiais de Construção

Introdução

O post de hoje é sobre um projeto pessoal que desenvolvi para atender à necessidade de economizar na construção de uma casa no sítio dos meus pais, localizado na cidade de Pato Branco, Paraná. Como estamos na etapa de aquisição de materiais de construção, a realização de múltiplos orçamentos se tornou indispensável. Para tornar esse processo mais eficiente, criei uma aplicação que se integra ao site Menor Preço - Nota Paraná, permitindo consultar produtos com notas fiscais emitidas no dia atual. Dessa forma, é possível identificar o estabelecimento que comercializou um determinado produto pelo menor preço.

Além de Pato Branco, posso adquirir materiais de construção em cidades próximas, como Francisco Beltrão, Vitorino, Coronel Vivida e Clevelândia. Por isso, configurei a aplicação para buscar informações dessas localidades, ampliando as opções e aumentando as chances de encontrar preços mais competitivos.

Para tornar as respostas mais amigáveis e inteligentes, conectei os dados coletados a um chatbot utilizando o modelo GPT-4o da OpenAI. O sistema foi desenvolvido com uma aplicação backend em Java 23, utilizando o framework Spring Boot com a lib Spring Boot AI, e um frontend construído com Angular 19.

Resultado final

Quero iniciar o post apresentando o resultado final da implementação e como estou utilizando a ferramenta na prática. Abaixo, compartilho alguns exemplos de perguntas e respostas realizadas no chat para ilustrar seu funcionamento.

menor preco exemplo 3
Figure 1. Chatbot retornando qual estabelecimento comercializou o saco de cimento de 50kg pelo menor preço
menor preco exemplo 2
Figure 2. Chatbot respondendo quais foram as 3 vendas de pregos com o menor preço
menor preco exemplo 1
Figure 3. Chatbot indicando quais as melhores opções de compras para tijolos

Repositório GIT

O código-fonte dos projetos está disponível no GitHub e pode ser acessado nos seguintes repositórios:

Direto ao ponto

A ideia deste post é ser o mais objetivo e técnico possível, demostrando o código-fonte e as partes relevantes da implementação, sem dar voltas ou prolongar explicações.

Projeto Backend

Arquivo pom.xml

No arquivo pom.xml, definimos as dependências do Spring Boot e do spring-ai-openai-spring-boot-starter, que possibilitam a conexão com a API da OpenAI:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.4.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.gasparbarancelli</groupId>
	<artifactId>menor-preco</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>menor-preco</name>
	<description>Demo project for Spring Boot</description>
	<url/>
	<licenses>
		<license/>
	</licenses>
	<developers>
		<developer/>
	</developers>
	<scm>
		<connection/>
		<developerConnection/>
		<tag/>
		<url/>
	</scm>
	<properties>
		<java.version>23</java.version>
		<spring-ai.version>1.0.0-M4</spring-ai.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.ai</groupId>
			<artifactId>spring-ai-openai-spring-boot-starter</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-devtools</artifactId>
			<scope>runtime</scope>
			<optional>true</optional>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<dependencyManagement>
		<dependencies>
			<dependency>
				<groupId>org.springframework.ai</groupId>
				<artifactId>spring-ai-bom</artifactId>
				<version>${spring-ai.version}</version>
				<type>pom</type>
				<scope>import</scope>
			</dependency>
		</dependencies>
	</dependencyManagement>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

	<repositories>
		<repository>
			<id>spring-milestones</id>
			<name>Spring Milestones</name>
			<url>https://repo.spring.io/milestone</url>
			<snapshots>
				<enabled>false</enabled>
			</snapshots>
		</repository>
	</repositories>

</project>

As configurações mais importantes aqui são:

  • A dependência spring-ai-openai-spring-boot-starter.

  • O BOM (spring-ai-bom) para gerenciar versões de dependência.

Arquivo application.properties

Neste arquivo, configuramos a chave da API (spring.ai.openai.api-key) e definimos o modelo do ChatGPT e as funções que serão usadas:

spring.application.name=menor-preco

spring.ai.openai.api-key=${API_KEY}
spring.ai.openai.chat.options.model=gpt-4o
spring.ai.openai.chat.options.functions=functionSearchCategories,functionSearchProducts

Observação: Substitua ${API_KEY} pela sua chave secreta da OpenAI, ou informe como variável de ambiente.

MenorPrecoApplication.java

Classe principal que inicia a aplicação Spring Boot:

package com.gasparbarancelli;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class MenorPrecoApplication {

	public static void main(String[] args) {
		SpringApplication.run(MenorPrecoApplication.class, args);
	}

}

ChatbotApi.java

Cria um endpoint HTTP que recebe a mensagem do usuário (/) e interage com o modelo da OpenAI via Spring AI. A mensagem do usuário é adicionada à lista de mensagens, enviamos a prompt para o ChatGPT e retornamos a resposta em texto puro.

package com.gasparbarancelli.transport.http;

import org.springframework.ai.chat.messages.AssistantMessage;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.openai.OpenAiChatModel;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.ArrayList;
import java.util.List;

@RestController
@RequestMapping("/")
public class ChatbotApi {

    private final OpenAiChatModel openAiChatClient;
    private List<Message> messages = new ArrayList<>();

    public ChatbotApi(OpenAiChatModel openAiChatClient) {
        this.openAiChatClient = openAiChatClient;
        messages.add(new SystemMessage("""
                Você é um assistente especialista em compras, dedicado a ajudar os usuários a economizar.
                Sua missão é fornecer sugestões detalhadas e precisas sobre os produtos mais econômicos disponíveis.

                Diretrizes para suas respostas:
                1. Sempre priorize listar os produtos com os menores preços e ofereça detalhes relevantes, como:
                   - Nome do produto
                   - Preço
                   - Estabelecimento
                   - Endereço (se disponível)

                2. Responda sempre no formato Markdown, garantindo que suas mensagens sejam claras e visualmente organizadas.

                3. Utilize os seguintes elementos do Markdown:
                   - Títulos para organizar a informação.
                   - Texto em negrito e itálico para destacar informações importantes.
                   - Listas ordenadas ou não ordenadas para apresentar múltiplas opções de forma legível.
                   - Blocos de código, se necessário, para apresentar dados técnicos.
                   - Links para direcionar o usuário a mais informações ou compras online.

                4. Certifique-se de que o Markdown gerado seja bem estruturado e fácil de entender.

                Sua prioridade é ajudar o usuário a economizar de forma eficiente e fornecer respostas que transmitam confiança e profissionalismo.
                """));

    }

    @GetMapping
    public String hello(@RequestParam("message") String message) {
        UserMessage userMessage = new UserMessage(message);
        messages.add(userMessage);

        ChatResponse response = openAiChatClient.call(new Prompt(messages));

        var content = response.getResult().getOutput().getContent();
        messages.add(new AssistantMessage(content));
        return content;
    }

}

Pontos de destaque:

  • messages mantém o histórico do chat, começando com uma SystemMessage que configura o papel do assistente.

  • A cada requisição GET, o parâmetro message é adicionado como uma UserMessage.

  • O openAiChatClient.call() envia esse contexto para a OpenAI e retorna a resposta do modelo.

ProductService.java

Classe responsável por chamar a API do Menor Preço e buscar informações de categorias e produtos em lote.

package com.gasparbarancelli.interactors;

import com.gasparbarancelli.entities.CategoryResponse;
import com.gasparbarancelli.entities.Location;
import com.gasparbarancelli.entities.ProductResponse;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestClient;

import java.util.ArrayList;
import java.util.List;

@Service
public class ProductService {

    private static final String CATEGORY_API_URL = "https://menorpreco.notaparana.pr.gov.br/api/v1/categorias";
    private static final String PRODUCT_API_URL = "https://menorpreco.notaparana.pr.gov.br/api/v1/produtos";
    private static final int BATCH_SIZE = 50;

    private final RestClient restClient;

    public ProductService() {
        this.restClient = RestClient.builder().build();
    }

    public List<CategoryResponse.Category> fetchCategories(String product) {
        try {
            String url = String.format("%s?local=%s&termo=%s&raio=2",
                    CATEGORY_API_URL, Location.PATO_BRANCO.getCode(), product);

            CategoryResponse response = restClient.method(HttpMethod.GET)
                    .uri(url)
                    .header(HttpHeaders.ACCEPT, "application/json")
                    .retrieve()
                    .body(CategoryResponse.class);

            return response.categories();
        } catch (Exception e) {
            throw new RuntimeException("Error fetching categories for product: " + product, e);
        }
    }

    public List<ProductResponse.Product> searchProductsInLocation(String location, String product, String categoryId) {
        List<ProductResponse.Product> allProducts = new ArrayList<>();
        int offset = 0;

        while (true) {
            try {
                String url = String.format("%s?local=%s&termo=%s&categoria=%s&offset=%d&raio=2&data=-1&ordem=0",
                        PRODUCT_API_URL, location, product, categoryId, offset);

                ProductResponse response = restClient.method(HttpMethod.GET)
                        .uri(url)
                        .header(HttpHeaders.ACCEPT, "application/json")
                        .retrieve()
                        .body(ProductResponse.class);

                if (response.products() != null && !response.products().isEmpty()) {
                    allProducts.addAll(response.products());
                }

                if (response.products() == null || response.products().size() < BATCH_SIZE) {
                    break;
                }

                offset += BATCH_SIZE;
            } catch (Exception e) {
                throw new RuntimeException("Error fetching products for category: " + categoryId, e);
            }
        }
        return allProducts;
    }

}

Detalhes:

  • fetchCategories(…​): faz a busca de categorias no Menor Preço, sempre iniciando por Pato Branco (conforme exemplo).

  • searchProductsInLocation(…​): pagina os resultados (utilizando offset) até não haver mais dados de produtos.

FunctionSearchCategories.java

Implementa a primeira função que o ChatGPT chama, para descobrir a categoria de um determinado produto.

package com.gasparbarancelli.interactors;

import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.gasparbarancelli.entities.CategoryResponse;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.function.Function;

@Service
@Description("Antes de conseguirmos as informações dos materiais de construções precisamos identificar a sua categoria")
public class FunctionSearchCategories implements Function<FunctionSearchCategories.Request, FunctionSearchCategories.Response> {

    private final ProductService productService;

    public FunctionSearchCategories(ProductService productService) {
        this.productService = productService;
    }

    @JsonClassDescription("Informações referentes as categorias de um material de construção, como quantidade, nome e seu identificador")
    public record Request(String product) {}
    public record Response(List<CategoryResponse.Category> categories) {}

    @Override
    public Response apply(Request request) {
        var product = request.product();
        return new Response(productService.fetchCategories(product));
    }

}

O fluxo é:

  • Dado um product, chamar productService.fetchCategories(…​).

  • Retornar a lista de categorias encontradas.

FunctionSearchProducts.java

Implementa a segunda função que o ChatGPT pode chamar, para buscar produtos dentro de uma categoria e em determinadas cidades. Aqui usamos Java Virtual Threads (criados no Java 21) para fazer consultas paralelas.

package com.gasparbarancelli.interactors;

import com.fasterxml.jackson.annotation.JsonClassDescription;
import com.gasparbarancelli.entities.Location;
import com.gasparbarancelli.entities.ProductResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Description;
import org.springframework.stereotype.Service;

import java.util.Comparator;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import java.util.stream.Stream;

@Service
@Description("Recupera as notas fiscais de produtos comercializados no dia atual, podendo identificar qual foi o preco mais alto ou baixo pago por um produto")
public class FunctionSearchProducts implements Function<FunctionSearchProducts.Request, FunctionSearchProducts.Response> {

    private static final Logger LOGGER = LoggerFactory.getLogger(FunctionSearchProducts.class);

    private final ProductService productService;

    public FunctionSearchProducts(ProductService productService) {
        this.productService = productService;
    }

    @JsonClassDescription("Informações referentes ao material de construção, como descrição, preço e estabelecimento em qual o material foi comercializado")
    public record Request(String product, String categoryId) {}
    public record Response(List<ProductResponse.Product> products) {}

    @Override
    public Response apply(Request request) {
        var product = request.product();
        var categoryId = request.categoryId();

        List<String> locationCodes = Location.getCodes();

        try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
            List<Future<List<ProductResponse.Product>>> futures = locationCodes.stream()
                    .map(location -> executor.submit(() -> productService.searchProductsInLocation(location, product, categoryId)))
                    .toList();

            return new Response(futures.stream()
                    .flatMap(future -> {
                        try {
                            return future.get(30, TimeUnit.SECONDS).stream();
                        } catch (Exception e) {
                            LOGGER.info("Erro ou timeout para a localização: " + e.getMessage());
                            return Stream.empty();
                        }
                    })
                    .sorted(Comparator.comparing(ProductResponse.Product::price))
                    .toList());
        }
    }

}

Fluxo:

  • Recebe product e categoryId como parâmetros.

  • Recupera os códigos das cidades (Location.getCodes()).

  • Para cada código de cidade, chama productService.searchProductsInLocation(…​) em paralelo.

  • Ordena todos os produtos obtidos pelo menor preço e retorna ao usuário.

Entidades

ProductResponse.java

package com.gasparbarancelli.entities;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonPropertyDescription;

import java.util.List;

public record ProductResponse(
    @JsonProperty("produtos") List<Product> products,
    @JsonProperty("total") int total,
    @JsonProperty("precos") ProductPrice price
) {
    public record ProductPrice(
            String min,
            String max
    ) {}

    public record Product(
        @JsonProperty("desc") String description,
        @JsonProperty("valor") String price,
        @JsonProperty("valor_desconto") String discount,
        @JsonProperty("estabelecimento") Company company
    ) {
        public record Company(
            @JsonPropertyDescription("Nome do estabelecimento em que o produto foi vendido")
            @JsonProperty("nm_fan") String name,
            @JsonProperty("tp_logr") String addressType,
            @JsonProperty("nm_logr") String addressName,
            @JsonPropertyDescription("Cidade em que o produto foi comercializado")
            @JsonProperty("mun") String city,
            String uf
        ) {}
    }
}

Guarda a estrutura JSON que recebemos do Menor Preço: lista de produtos, quantidade total e uma faixa de preço (mínimo e máximo).

Location.java

package com.gasparbarancelli.entities;

import java.util.Arrays;
import java.util.List;

public enum Location {

    PATO_BRANCO("6g6dc3jcw"),
    CLEVELANDIA("6g6cb9qch"),
    VITORINO("6g66wfwkw"),
    CORONEL_VIVIDA("6g6s5y7j2"),
    FRANCISCO_BELTRAO("6g678srwp");

    private String code;

    Location(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }

    public static List<String> getCodes() {
        return Arrays.stream(Location.values()).map(Location::getCode).toList();
    }

}

Enum que mapeia as cidades para seus respectivos códigos na API do Menor Preço.

CategoryResponse.java

package com.gasparbarancelli.entities;

import com.fasterxml.jackson.annotation.JsonProperty;

import java.util.List;

public record CategoryResponse(
    @JsonProperty("categorias") List<Category> categories
) {
    public record Category(
        String id,
        @JsonProperty("qtd") Integer qtd,
        @JsonProperty("desc") String name
    ) {}
}

Modelo para representar as categorias retornadas pela API.

Configuração de CORS

Para permitir que o frontend Angular acesse nossa API em outro endereço (porta 8080 vs 4200), precisamos liberar o CORS:

package com.gasparbarancelli.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig {

    @Bean
    public WebMvcConfigurer corsConfigurer() {
        return new WebMvcConfigurer() {
            @Override
            public void addCorsMappings(CorsRegistry registry) {
                registry.addMapping("/**")
                        .allowedOrigins("http://localhost:4200")
                        .allowedMethods("GET", "OPTIONS")
                        .allowedHeaders("*")
                        .allowCredentials(true);
            }
        };
    }
}

Projeto Frontend

chat.component.ts

Aqui, enviamos o texto digitado para o ChatService e, em seguida, adicionamos a mensagem de resposta do servidor no array messages.

import { Component } from '@angular/core';
import { ChatService } from './chat.service';

export interface ChatMessage {
  message: string;
  send: boolean;
}

@Component({
  selector: 'app-chat',
  standalone: false,
  templateUrl: './chat.component.html',
  styleUrls: ['./chat.component.scss']
})
export class ChatComponent {

  messages: ChatMessage[] = [];
  inputMessage: string = '';

  constructor(private chatService: ChatService) {}

  sendMessage(): void {
    if (this.inputMessage.trim()) {
      const userMessage: ChatMessage = { message: this.inputMessage, send: true };
      this.messages.push(userMessage);

      this.chatService.send(this.inputMessage).subscribe({
        next: response => {
          const botMessage: ChatMessage = { message: response, send: false };
          this.messages.push(botMessage);
        },
        error: error => {
          console.error('Erro ao enviar mensagem:', error);
      }});
      this.inputMessage = '';
    }
  }
}

chat.component.html

Percorremos o array de mensagens para exibir as mensagens do chat enviadas pelo usuário e pelo chatbot, bem como adicionar um input onde o usuário entra com a mensagens e um botão para enviar a mensagem.

<div class="chat-container">
  <div class="chat-messages">
    <div *ngFor="let msg of messages"
      [ngClass]="{ 'message-send': msg.send, 'message-receive': !msg.send }"
      class="message">
      <markdown
        [data]="msg.message"
        [disableSanitizer]="true">
      </markdown>
    </div>
  </div>
  <div class="chat-input">
    <input
      type="text"
      [(ngModel)]="inputMessage"
      placeholder="Digite uma mensagem"
      (keydown.enter)="sendMessage()"/>
    <button (click)="sendMessage()">Enviar</button>
  </div>
</div>

chat.component.scss

Definimos um estilo para o nosso componente de Chat.

.chat-container {
  display: flex;
  flex-direction: column;
  height: 100%;
  border: 1px solid #ccc;
  border-radius: 8px;
  padding: 10px;
}

.chat-messages {
  flex: 1;
  overflow-y: auto;
  margin-bottom: 10px;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.message {
  padding: 10px;
  border-radius: 8px;
  max-width: 60%;
  word-wrap: break-word;
}

.message-send {
  align-self: flex-end;
  background-color: #007bff;
  color: white;
  text-align: left;
}

.message-receive {
  align-self: flex-start;
  background-color: #f1f0f0;
  color: black;
  text-align: left;
}

.chat-input {
  display: flex;
  gap: 5px;
}

.chat-input input {
  flex: 1;
  padding: 10px;
  border: 1px solid #ccc;
  border-radius: 8px;
}

.chat-input button {
  padding: 10px;
  background-color: #007bff;
  color: white;
  border: none;
  border-radius: 8px;
  cursor: pointer;
}

.chat-input button:hover {
  background-color: #0056b3;
}

chat.service.ts

Serviço que se comunica com nosso backend (localhost:8080) para enviar a mensagem.

import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {Observable} from 'rxjs';

@Injectable()
export class ChatService {

  constructor(private httpClient: HttpClient) {
  }

  send(message: string): Observable<string> {
    const url = `http://localhost:8080/?message=${message}`;
    return this.httpClient.get(url, {responseType: 'text'});
  }

}

chat.module.ts

Este módulo registra o ChatComponent, ChatService e o suporte a Markdown.

import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {ChatComponent} from './chat.component';
import {ChatService} from './chat.service';
import {FormsModule} from '@angular/forms';
import {MarkdownComponent, provideMarkdown} from 'ngx-markdown';

@NgModule({
  declarations: [
    ChatComponent
  ],
  exports: [
    ChatComponent
  ],
  imports: [
    CommonModule,
    FormsModule,
    MarkdownComponent
  ],
  providers: [
    ChatService,
    provideMarkdown()
  ]
})
export class ChatModule {
}

Instalação de pacote

O único componente necessário para instalação é aquele responsável por renderizar Markdown. Para isso, execute o seguinte comando:

npm install ngx-markdown marked@^15.0.0 --save

app.component.html

Adicionamos apenas o componente de chat.

<app-chat></app-chat>

app.component.ts

Seguimos com a implementação padrão.

import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  standalone: false,
})
export class AppComponent {
}

app.module.ts

Declaramos o modulo do componente de Chat bem como do HttpClientModule, para que possamos realizar chamadas http para o nosso backend.

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import {ChatModule} from './chat/chat.module';
import {HttpClientModule} from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    AppRoutingModule,
    ChatModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Por fim, basta rodar o ng serve no frontend e mvn spring-boot:run no backend.

Conclusão

A ideia central deste projeto foi demonstrar o poder que temos ao integrar uma fonte de dados ao chatbot, no meu caso agora eu tenho maior chance de economizar na compra de materiais de construção, integrando o ChatGPT (via funções específicas) com a API do Menor Preço do Nota Paraná.

De forma que:

  • Usuário pergunta: “Qual o menor preço para cimento hoje?”

  • ChatGPT recebe a pergunta e chama internamente a função functionSearchCategories para descobrir a categoria de “cimento”.

  • Em seguida, chama a função functionSearchProducts informando o categoryId retornado.

  • O sistema retorna, em formato Markdown, a lista de estabelecimentos e preços (do menor para o maior) para o produto “cimento” nas cidades de interesse (Pato Branco, Clevelandia, Vitorino, Coronel Vivida e Francisco Beltrão).

Com isso, posso entrar em contato com o estabelecimento que está oferecendo o melhor custo e efetuar a compra!

// Compartilhe esse Post

💫
🔥 NOVO APP

Domine o Inglês em 30 dias!

Inteligência Artificial + Repetição Espaçada • Método cientificamente comprovado

✅ Grátis para começar 🚀 Resultados rápidos
×