paint-brush
Como construir um aplicativo de assistente pessoal baseado em pesquisa de IApor@lablab
6,056 leituras
6,056 leituras

Como construir um aplicativo de assistente pessoal baseado em pesquisa de IA

por lablab.ai hackathons28m2023/07/04
Read on Terminal Reader

Muito longo; Para ler

A Anthropic é uma organização de pesquisa focada no desenvolvimento de sistemas avançados de IA. Sua última criação, Claude, é um assistente de IA de última geração projetado para ser útil, honesto e inofensivo. LangChain é uma ferramenta para construir aplicativos de modelo de linguagem de ponta a ponta. Ele fornece uma estrutura robusta que simplifica o processo de criação, gerenciamento e implantação de modelos de aprendizado de idiomas.
featured image - Como construir um aplicativo de assistente pessoal baseado em pesquisa de IA
lablab.ai hackathons HackerNoon profile picture
0-item
1-item

Neste artigo, gostaríamos de mostrar como combinar duas tecnologias ( LangChain e Anthropic ) e criar um assistente pessoal com motor de busca.


Mais e mais produtos de IA estão sendo criados pelos participantes de nossos hackathons. A próxima oportunidade para desenvolvedores é nosso hackathon conjunto com o Google Cloud Vertex AI , onde cada participante tem a chance de criar seu próprio aplicativo de IA usando a tecnologia mais recente do Google Cloud . Aqui falaremos sobre uma aplicação de IA que está se tornando cada vez mais importante: os mecanismos de busca .



O que é?

Um assistente pessoal com tecnologia de pesquisa é um assistente digital que usa tecnologia de mecanismo de pesquisa para ajudar os usuários em tarefas como encontrar informações, fazer reservas, definir lembretes e enviar mensagens. Esses assistentes usam algoritmos de pesquisa para coletar e analisar dados de várias fontes, apresentando-os aos usuários de maneira útil e concisa.


Exemplos proeminentes de assistentes pessoais baseados em pesquisa são Google Assistant , Siri , Alexa e Cortana . Esses assistentes usam efetivamente seus recursos de pesquisa para fornecer informações precisas e relevantes, concluir tarefas e melhorar suas respostas à medida que interagem com o usuário.


Apresentando o Claude do Anthropic

A Anthropic é uma organização de pesquisa focada no desenvolvimento de sistemas avançados de IA. Sua última criação, Claude, é um assistente de IA de próxima geração projetado para ser útil, honesto e inofensivo. Este modelo de ponta garante um alto grau de confiabilidade e previsibilidade em diversas tarefas.


Os principais recursos do Claude incluem:

  • Recursos versáteis de conversação e processamento de texto

  • Manter a segurança e a privacidade do usuário como sua principal prioridade


Os principais casos de uso de Claude são:

  • resumo

  • Procurar

  • Escrita criativa e colaborativa

  • perguntas e respostas

  • Assistência de codificação


Esses recursos tornam o Claude uma ferramenta de IA ideal para uma variedade de aplicativos, capacitando usuários em diferentes domínios.

Introdução ao LangChain

LangChain é uma ferramenta versátil para construir aplicativos de modelo de linguagem de ponta a ponta. Ele fornece uma estrutura robusta que simplifica o processo de criação, gerenciamento e implantação de modelos de aprendizado de idiomas (LLMs). Os LLMs são modelos avançados de IA projetados para entender, gerar e manipular texto humano em vários idiomas e tarefas.


Os principais recursos do LangChain incluem:


  • Gerenciamento eficiente de prompts para LLMs

  • A capacidade de criar cadeias de tarefas para fluxos de trabalho complexos

  • Adicionando estado à IA, permitindo que ela se lembre de informações de interações anteriores


Esses recursos tornam o LangChain uma plataforma poderosa e fácil de usar para aproveitar o potencial dos modelos de linguagem em diversas aplicações.

Pré-requisitos

  • Conhecimento básico de Python
  • Conhecimento básico de Typescipt e/ou React
  • Acesso à API Claude da Anthropic
  • Acesso à API de pesquisa na web do SerpApi

Contorno

  1. Inicializando o Projeto
  2. Construindo o front-end para um aplicativo AI Assistant com Claude e LangCHain
  3. Escrevendo os arquivos do projeto
  4. Testando o aplicativo AI Assistant

Discussão

Inicializando o Projeto

app.py (ponto de entrada do aplicativo Flask) 🐍

  1. Instalar o Flask : Para começar, certifique-se de ter o Flask instalado em seu ambiente. Você pode fazer isso usando pip :

     pip install Flask


  2. Crie um novo diretório : Crie um novo diretório para o seu projeto e navegue até ele:

     mkdir claude-langchain cd claude-langchain


  3. Configure um ambiente virtual (opcional) : é uma boa prática usar um ambiente virtual ao trabalhar com projetos Python. Você pode criar um usando venv ou qualquer outra ferramenta de sua escolha:

     python -m venv venv source venv/bin/activate (Linux/Mac) venv\Scripts\activate (Windows)


  4. Crie um arquivo main.py : Crie um arquivo main.py para escrever o código do aplicativo Flask:

     touch app.py # Linux/Mac echo.>app.py # Windows


  5. Escreva o código do aplicativo Flask : Abra o arquivo main.py em seu editor de código favorito e adicione o seguinte código:

     from flask import Flask app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run()


  6. Execute o aplicativo Flask : salve o arquivo main.py e execute o seguinte comando em seu terminal/prompt de comando:

     python main.py


  7. Abra seu navegador : Abra seu navegador preferido e navegue até http://127.0.0.1:5000/ . Você deve ver "Hello, World!" exibido na página da web.


E é isso! Você inicializou com sucesso um projeto Flask e criou um aplicativo simples.

.env 🌏

  1. Instale python-dotenv e langchain : Para gerenciar facilmente as variáveis de ambiente com um arquivo .env , usaremos o pacote python-dotenv . Ao mesmo tempo, vamos também instalar langchain . Instale ambos os pacotes usando pip :

     pip install python-dotenv langchain


  2. Crie um arquivo .env : Crie um arquivo .env no diretório raiz do seu projeto:

     touch .env # Linux/Mac echo.>.env # Windows


  3. Adicionar variáveis de ambiente : Abra o arquivo .env em seu editor de código favorito e adicione suas variáveis de ambiente. Cada variável deve estar em uma nova linha, no formato CHAVE=VALOR:

     ANTHROPIC_API_KEY=sk-ant-xxxxxxxxxxxxxxxxx SERPAPI_API_KEY=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

    Observe que você precisa ter a chave de API para o modelo Claude da Anthropic e o serviço de pesquisa na web da SerpAPI .


  4. Carregar variáveis de ambiente : Modifique seu arquivo main.py para carregar as variáveis de ambiente do arquivo .env usando python-dotenv . Atualize seu main.py da seguinte maneira:

     import os from flask import Flask from dotenv import load_dotenv load_dotenv() app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' if __name__ == '__main__': app.run()


  5. Ignore o arquivo .env no controle de versão: é importante não compartilhar informações confidenciais como chaves secretas no controle de versão. Se você estiver usando o Git, adicione a seguinte linha ao seu arquivo .gitignore (crie um caso não o tenha):

     .env


Agora, seu projeto Flask está configurado para usar variáveis de ambiente do arquivo .env . Você pode adicionar mais variáveis conforme necessário e acessá-las usando os.environ.get('KEY') . Lembre-se de manter o arquivo .env privado e nunca submetê-lo ao controle de versão.

Construindo o front-end para um aplicativo AI Assistant com Claude e LangChain

Este tutorial foi desenvolvido para usuários intermediários que possuem um conhecimento básico de Node.js, npm, React e Typescript. Usaremos uma pilha que inclui essas tecnologias, junto com Tailwind CSS para estilização. Essa pilha foi escolhida por sua robustez, versatilidade e forte suporte da comunidade para essas tecnologias. Além disso, integraremos o modelo Claude da Anthropic e LangChain, duas poderosas tecnologias de IA que permitirão que nosso aplicativo gere respostas de texto precisas e humanas.

Instalando Node.js e NPM

  1. Baixe o instalador do Node.js para seu sistema operacional no site oficial .

  2. Siga os prompts de instalação para instalar o Node.js e o npm. A versão LTS (Long Term Support) é recomendada para a maioria dos usuários.

  3. Uma vez instalado, verifique a instalação verificando as versões do Node.js e npm do seu terminal:

    nó -v npm -v

Configurando o ambiente do projeto

Instalando Criar Aplicativo React

Create React App (CRA) é um utilitário de linha de comando que nos auxilia na criação de um novo aplicativo React.js. Vamos instalá-lo globalmente via npm:

 npm install -g create-react-app
Criando um novo projeto React com Typescript

Usaremos o CRA com o modelo Typescript para criar um novo projeto chamado ai-assistant-claude .

 npx create-react-app ai-assistant-claude --template typescript

Este comando cria um novo diretório chamado ai-assistant-claude em nosso diretório atual, abrigando um novo aplicativo React com suporte Typescript.

Integrando TailwindCSS

Instalando o TailwindCSS

As etapas seguidas neste tutorial são baseadas na documentação oficial do Tailwind CSS . Consulte estes documentos para obter instruções mais atualizadas.

Começaremos instalando o TailwindCSS e inicializando a biblioteca em nosso projeto:

 npm install -D tailwindcss npx tailwindcss init
Configurando caminhos de modelo

Em seguida, configuramos nossos caminhos de modelo adicionando-os ao arquivo tailwind.config.js . O ++ significa as linhas que você adicionará:

 /** @type {import('tailwindcss').Config} */ module.exports = { -- content: [], ++ content: [ ++ "./src/**/*.{js,jsx,ts,tsx}", ++ ], theme: { extend: {}, }, plugins: [], }
Adicionando Diretivas Tailwind

Por fim, adicionaremos as diretivas @tailwind para cada uma das camadas do Tailwind ao nosso arquivo ./src/index.css :

 @tailwind base; @tailwind components; @tailwind utilities;

E voilá! Tailwind CSS agora está integrado ao nosso projeto.

Instalando as Bibliotecas Necessárias

Antes de prosseguirmos para a seção de codificação, vamos finalizar nossos preparativos instalando as bibliotecas necessárias, como fontawesome , react-markdown , axios e react-hook-form .

Instalando o FontAwesome para Ícones
 npm i --save @fortawesome/fontawesome-svg-core npm install --save @fortawesome/free-solid-svg-icons npm install --save @fortawesome/react-fontawesome
Instalando o React Markdown para renderizar o conteúdo do Markdown
 npm install --save react-markdown

Com essas etapas concluídas, seu projeto está configurado com todas as ferramentas e bibliotecas necessárias. Em seguida, começaremos a criar o aplicativo assistente de IA que usa a API Claude da Anthropic e o LangChain para gerar respostas de texto precisas e semelhantes às humanas.

Solução de problemas

Se você tiver algum problema durante a instalação ou configuração, aqui estão algumas soluções comuns:


Para quaisquer outros problemas, consulte a documentação das respectivas bibliotecas ou poste seus problemas no StackOverflow ou nos repositórios relevantes do GitHub.

Escrevendo os arquivos do projeto

Nesta seção, voltaremos ao aplicativo Flask que inicializamos anteriormente e adicionaremos novos endpoints, como /ask e /search . Eles servirão como pontos de extremidade para nossos recursos de bate-papo simples e avançado (o último sendo alimentado pelos resultados de pesquisa do Google).

Vamos começar importando nossos módulos necessários:


 from flask import Flask, jsonify, request from dotenv import load_dotenv from langchain.chat_models import ChatAnthropic from langchain.chains import ConversationChain from langchain.agents import Tool from langchain.agents import AgentType from langchain.utilities import SerpAPIWrapper from langchain.agents import initialize_agent from langchain.memory import ConversationBufferMemory from langchain.prompts.chat import ( ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate, ) load_dotenv() app = Flask(__name__)

A seção acima importa todos os pacotes necessários e inicializa nosso aplicativo Flask.

Desenvolvendo o back-end

Criando os endpoints básicos

Começaremos criando um endpoint básico ( / ) para testar se nosso servidor está rodando corretamente:

 @app.route('/') def hello_world(): return 'Hello, World!'

Ao visitar a URL raiz, devemos receber a resposta "Hello, World!", indicando que nosso servidor está funcionando conforme o esperado.

Criando o ponto de extremidade /ask

Este terminal processa mensagens para o recurso de bate-papo básico de nosso aplicativo. Ele lê os dados JSON da solicitação, processa as mensagens e gera uma resposta usando o modelo Claude da Anthropic e LangChain.

 @app.route('/ask', methods=['POST']) def ask_assistant(): # The code for /ask endpoint goes here


Extraindo mensagens da solicitação

Primeiro, precisamos verificar se algum dado foi fornecido e extrair as mensagens dele.

 data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message")


Gerando a resposta

O segmento de código a seguir gera a resposta do chat usando o modelo ChatAnthropic() do LangChain e o ChatPromptTemplate para estruturar nossa conversa. O histórico da conversa é armazenado usando o ConversationBufferMemory .


 llm = ChatAnthropic() input = "" message_list = [] for message in messages: if message['role'] == 'user': message_list.append( HumanMessagePromptTemplate.from_template(message['content']) ) input = message['content'] elif message['role'] == 'assistant': message_list.append( AIMessagePromptTemplate.from_template(message['content']) ) # Adding SystemMessagePromptTemplate at the beginning of the message_list message_list.insert(0, SystemMessagePromptTemplate.from_template( "The following is a friendly conversation between a human and an AI. The AI is talkative and " "provides lots of specific details from its context. The AI will respond with plain string, replace new lines with \\n which can be easily parsed and stored into JSON, and will try to keep the responses condensed, in as few lines as possible." )) message_list.insert(1, MessagesPlaceholder(variable_name="history")) message_list.insert(-1, HumanMessagePromptTemplate.from_template("{input}")) prompt = ChatPromptTemplate.from_messages(message_list) memory = ConversationBufferMemory(return_messages=True) conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm) result = conversation.predict(input=input)

Enviando a resposta

Depois de gerar a resposta, substituímos as novas linhas no resultado e o retornamos como um objeto JSON.

 print(result) return jsonify({"status": "success", "message": result})
Criando o ponto de extremidade /search

O ponto de extremidade /search é semelhante a /ask , mas inclui a funcionalidade de pesquisa para fornecer respostas mais detalhadas. Usamos o SerpAPIWrapper para adicionar esse recurso.

 @app.route('/search', methods=['POST']) def search_with_assistant(): data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message") llm = ChatAnthropic() # Get the last message with 'user' role user_messages = [msg for msg in messages if msg['role'] == 'user'] last_user_message = user_messages[-1] if user_messages else None # If there is no user message, return an error response if not last_user_message: return jsonify({"error": "No user message found"}), 400 input = last_user_message['content'] search = SerpAPIWrapper() tools = [ Tool( name = "Current Search", func=search.run, description="useful for when you need to answer questions about current events or the current state of the world" ), ] chat_history = MessagesPlaceholder(variable_name="chat_history") memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) agent_chain = initialize_agent( tools, llm, agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True, memory=memory, agent_kwargs = { "memory_prompts": [chat_history], "input_variables": ["input", "agent_scratchpad", "chat_history"] } ) result = agent_chain.run(input=input) print(result) return jsonify({"status": "success", "message": result})


Executando o aplicativo Flask

Por fim, adicionamos o clichê padrão para executar nosso aplicativo Flask.

 if __name__ == '__main__': app.run()
Testando o back-end

Se tudo correr bem, aqui está nosso código de back-end final.

 from flask import Flask, jsonify, request from dotenv import load_dotenv from langchain.chat_models import ChatAnthropic from langchain.chains import ConversationChain from langchain.agents import Tool from langchain.agents import AgentType from langchain.utilities import SerpAPIWrapper from langchain.agents import initialize_agent from langchain.memory import ConversationBufferMemory from langchain.prompts.chat import ( ChatPromptTemplate, MessagesPlaceholder, SystemMessagePromptTemplate, AIMessagePromptTemplate, HumanMessagePromptTemplate, ) load_dotenv() app = Flask(__name__) @app.route('/') def hello_world(): return 'Hello, World!' @app.route('/ask', methods=['POST']) def ask_assistant(): data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message") llm = ChatAnthropic() input = "" message_list = [] for message in messages: if message['role'] == 'user': message_list.append( HumanMessagePromptTemplate.from_template(message['content']) ) input = message['content'] elif message['role'] == 'assistant': message_list.append( AIMessagePromptTemplate.from_template(message['content']) ) # Adding SystemMessagePromptTemplate at the beginning of the message_list message_list.insert(0, SystemMessagePromptTemplate.from_template( "The following is a friendly conversation between a human and an AI. The AI is talkative and " "provides lots of specific details from its context. The AI will respond with plain string, replace new lines with \\n which can be easily parsed and stored into JSON, and will try to keep the responses condensed, in as few lines as possible." )) message_list.insert(1, MessagesPlaceholder(variable_name="history")) message_list.insert(-1, HumanMessagePromptTemplate.from_template("{input}")) prompt = ChatPromptTemplate.from_messages(message_list) memory = ConversationBufferMemory(return_messages=True) conversation = ConversationChain(memory=memory, prompt=prompt, llm=llm) result = conversation.predict(input=input) print(result) return jsonify({"status": "success", "message": result}) @app.route('/search', methods=['POST']) def search_with_assistant(): data = request.get_json() if not data: return jsonify({"error": "No data provided"}), 400 messages = data.get("message") llm = ChatAnthropic() # Get the last message with 'user' role user_messages = [msg for msg in messages if msg['role'] == 'user'] last_user_message = user_messages[-1] if user_messages else None # If there is no user message, return an error response if not last_user_message: return jsonify({"error": "No user message found"}), 400 input = last_user_message['content'] search = SerpAPIWrapper() tools = [ Tool( name = "Current Search", func=search.run, description="useful for when you need to answer questions about current events or the current state of the world" ), ] chat_history = MessagesPlaceholder(variable_name="chat_history") memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True) agent_chain = initialize_agent( tools, llm, agent=AgentType.STRUCTURED_CHAT_ZERO_SHOT_REACT_DESCRIPTION, verbose=True, memory=memory, agent_kwargs = { "memory_prompts": [chat_history], "input_variables": ["input", "agent_scratchpad", "chat_history"] } ) result = agent_chain.run(input=input) print(result) return jsonify({"status": "success", "message": result}) if __name__ == '__main__': app.run()


Agora, vamos testar nosso aplicativo. Execute o aplicativo de back-end com este comando, certifique-se também de que nosso ambiente virtual esteja ativado.

 flask run


Saberemos que tudo ficará bem se nosso terminal retornar esta saída.


Sem mais delongas, vamos testar nossos dois endpoints, /ask e /search . Para distinguir entre os dois, vamos enviar a cada um deles uma carga útil semelhante. Abra seu software de documentação e teste de API REST ou apenas use cURL. Mas neste tutorial, usarei Insomnia .


Vamos primeiro chamar o endpoint /ask com o seguinte payload. Digamos que perguntei sobre a sequência do videogame " The Legend of Zelda: Breath of the Wild ". Que responderá incorretamente. Isso é de se esperar já que o modelo tem corte de treinos a partir do final de 2021, período em que ainda não houve anúncios sobre a sequência.


E o ponto de extremidade /search ? Se você notar nosso código anterior, esse endpoint é tratado com uma cadeia mais sofisticada, que utiliza Agent.


Ao usar o Agent, podemos dar à IA mais poder na tomada de decisão, fornecendo-lhe mais ferramentas do que apenas o seu próprio modelo, que como já demonstrámos, tem as suas próprias falhas.


Para demonstrar como o endpoint /search funciona, vamos chamá-lo com a mesma carga de antes.


Desta vez, funcionou bem! Como funciona sob o capô? vamos ver a saída em nosso terminal.

Curiosamente, desta vez o modelo de IA não respondeu de imediato, mas pareceu 'ponderar' sobre o que fazer para responder. Ele decidiu responder depois de observar no resultado da pesquisa na web que "Com base na pesquisa de notícias, parece que uma sequência chamada The Legend of Zelda: Tears of the Kingdom foi anunciada".


Então, por essa lógica, não deveríamos apenas usar o ponto de extremidade /search ? pois é mais preciso e, portanto, mais inteligente? Não exatamente. Normalmente, em um aplicativo baseado em chatbot, espera-se que o bot retenha o contexto das conversas anteriores, para que possa fornecer respostas como se 'lembrasse' todo o contexto da conversa. Vamos ver se /search endpoint pode fazer exatamente isso.


Vamos testar se o endpoint /search pode lembrar o que acabamos de perguntar.


Isso aconteceu porque, embora a biblioteca LangChain tenha um recurso de cadeia de memória que pode ser usado para reter conversas anteriores, o servidor da Web e o serviço REST API construído nele são inerentemente sem estado. O que significa que o servidor da Web tratará cada solicitação como uma nova solicitação.


Mas não incluímos as conversas anteriores como carga útil? Esta é uma boa pergunta. Acontece que a cadeia de agentes usada no LangChain atualmente não suporta a manipulação de prompts compostos, que consistem em solicitações e respostas do usuário e da IA. Usamos isso principalmente para fornecer conversas de exemplo para o modelo, ajustando ainda mais o modelo para nossa resposta desejada.


Já o Agente trabalha recebendo uma única instrução e desenvolve suas cadeias de pensamento em torno dela. É por isso que, por mais longas que sejam nossas conversas, o agente só responderá à última solicitação.


Vamos testar nosso endpoint /ask para perceber a diferença.


Desta vez, ele respondeu usando nossas conversas anteriores como contexto adicional! Até agora, percebemos que precisamos de ambos os endpoints para criar nosso aplicativo AI Assistant. Mas como podemos incorporar o ponto de extremidade /ask desatualizado, mas sempre lembrar, com o ponto de extremidade /search esquecido, mas completo e atualizado? Construindo o front-end, é claro!

Desenvolvendo o Front-end


App.tsx

Vamos navegar de volta ao diretório do projeto ai-assistant-claude . Neste projeto, começaremos a modificar nossos componentes React, começando com o arquivo de ponto de entrada, App.tsx .

 import React from 'react'; import logo from './logo.svg'; import './App.css'; import { ChatClient } from './ChatClient'; function App() { return ( <div> <ChatClient/> </div> ); } export default App;

No trecho de código acima, importamos o componente ChatClient , que será criado em uma etapa posterior. Em seguida, incluímos o componente <ChatClient/> em um elemento <div> . Isso configura a estrutura para nosso aplicativo assistente de IA usando componentes React.

ChatClient.tsx

 import React, { useState } from 'react'; import { ChatInput } from './ChatInput'; import { ChatHistory } from './ChatHistory'; export interface Message { content: string; role: string; } export const ChatClient: React.FC = () => { const [messages, setMessages] = useState<Array<Message>>([]); const [isLoading, setIsLoading] = useState(false) const handleSimpleChat = (message: string) => { // Send the message and past chat history to the backend // Update messages state with the new message let newMessages = [...messages, { content: message, role: 'user' }] setMessages(newMessages); let postData = { message: newMessages } setIsLoading(true) fetch('/ask', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(postData), }) .then((response) => response.json()) .then((data) => { if (data.status === "success") { setMessages([...newMessages, { content: data.message, role: 'assistant' }]) } setIsLoading(false) console.log('Success:', data); }) .catch((error) => { console.error('Error:', error); setIsLoading(false) }); }; const handleAdvancedChat = (message: string) => { // Trigger AI agent with Google Search functionality // Update messages state with the new message and AI response let newMessages = [...messages, { content: message, role: 'user' }] setMessages(newMessages); let postData = { message: newMessages } setIsLoading(true) fetch('/search', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify(postData), }) .then((response) => response.json()) .then((data) => { if (data.status === "success") { setMessages([...newMessages, { content: data.message, role: 'assistant' }]) } console.log('Success:', data); setIsLoading(false) }) .catch((error) => { console.error('Error:', error); setIsLoading(false) }); }; return ( <div className="h-screen bg-gray-100 dark:bg-gray-900 flex items-center justify-center"> <div className='flex flex-col items-center gap-2'> <h1 className='text-white text-xl'>AI Assistant with Claude and LangChain</h1> <div className="w-full max-w-md h-[80vh] bg-white dark:bg-gray-800 rounded-lg shadow-md overflow-hidden flex flex-col"> <ChatHistory messages={messages} isLoading={isLoading} /> <ChatInput onSimpleChat={handleSimpleChat} onAdvancedChat={handleAdvancedChat} /> </div> </div> </div> ); };

Este componente serve como interface de usuário principal para nosso assistente de IA. Conforme mostrado, ele incorpora um componente ChatHistory para exibir o histórico da conversa e um componente ChatInput para inserir texto.


O componente processa a entrada do componente ChatInput , envia solicitações para o back-end usando essa entrada e exibe um status de carregamento. Se a solicitação for processada com sucesso, o componente também mostrará a resposta recebida do back-end.

ChatHistory.tsx

 import React from 'react'; import { ReactMarkdown } from 'react-markdown/lib/react-markdown'; import { Message } from './ChatClient'; interface ChatHistoryProps { messages: Array<Message>; isLoading: boolean } export const ChatHistory: React.FC<ChatHistoryProps> = ({ messages, isLoading }) => { return ( <div className="p-4 h-full overflow-y-auto"> {messages.map((message, index) => ( <div key={index} className={`mb-3 ${ message.role === 'user' ? 'text-right' : 'text-left' }`} > <ReactMarkdown className={`inline-block px-3 py-2 rounded-md ${ index % 2 === 0 ? 'bg-gray-300 dark:bg-gray-700' : 'bg-blue-200 dark:bg-blue-900' }`} > {message.content} </ReactMarkdown> </div> ))} {isLoading && ( <div className="mb-3 text-left"> <ReactMarkdown className="inline-block px-3 py-2 rounded-md bg-blue-200 dark:bg-blue-900 animate-pulse" > {/* Put your desired loading content here */} Thinking... </ReactMarkdown> </div> )} </div> ); };

Felizmente, o TailwindCSS oferece classes utilitárias integradas para animações simples, como animate-pulse . Essa classe ajuda elegantemente a indicar que uma solicitação está aguardando uma resposta. Neste componente também diferenciamos o posicionamento das mensagens do “usuário” e do “assistente”.


ChatInput.tsx

 import React, { useState } from 'react'; interface ChatInputProps { onSimpleChat: (message: string) => void; onAdvancedChat: (message: string) => void; } export const ChatInput: React.FC<ChatInputProps> = ({ onSimpleChat, onAdvancedChat }) => { const [input, setInput] = useState(''); const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>) => { setInput(event.target.value); }; const handleSubmit = (handler: (message: string) => void) => { handler(input); setInput(''); }; return ( <div className="flex p-4 border-t border-gray-200 dark:border-gray-700"> <input type="text" value={input} onChange={handleInputChange} placeholder="Type your message..." className="flex-grow px-3 py-2 rounded-md bg-gray-200 text-gray-900 dark:bg-gray-700 dark:text-gray-100 focus:outline-none" /> <button onClick={() => handleSubmit(onSimpleChat)} className="ml-2 px-4 py-2 font-semibold text-gray-600 bg-white dark:text-gray-400 dark:bg-gray-800 border border-gray-300 rounded-md hover:bg-gray-200 dark:hover:bg-gray-700 focus:outline-none" > Ask </button> <button onClick={() => handleSubmit(onAdvancedChat)} className="ml-2 px-4 py-2 font-semibold text-white bg-blue-500 border border-blue-600 rounded-md hover:bg-blue-400 focus:outline-none" > Ask and Search </button> </div> ); };

Por fim, adicionamos dois botões à nossa entrada de texto. O primeiro botão é usado para enviar a entrada para o endpoint /ask , que processa a entrada usando o modelo AI sem nenhum aprimoramento adicional.


Esse endpoint é sensível ao contexto. O segundo botão, apropriadamente denominado "Perguntar e pesquisar", envia a entrada para o terminal /search . Além de processar a entrada por meio do modelo de IA, esse botão também aciona uma pesquisa na Web orientada por IA, se a situação exigir.


index.html

 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="theme-color" content="#000000" /> <meta name="description" content="Web site created using create-react-app" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <!-- manifest.json provides metadata used when your web app is installed on a user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> <link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <!-- Notice the use of %PUBLIC_URL% in the tags above. It will be replaced with the URL of the `public` folder during the build. Only files inside the `public` folder can be referenced from the HTML. Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will work correctly both with client-side routing and a non-root public URL. Learn how to configure a non-root public URL by running `npm run build`. --> -- <title>React App</title> ++ <title>Claude AI Assistant</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> <!-- This HTML file is a template. If you open it directly in the browser, you will see an empty page. You can add webfonts, meta tags, or analytics to this file. The build step will place the bundled scripts into the <body> tag. To begin the development, run `npm start` or `yarn start`. To create a production bundle, use `npm run build` or `yarn build`. --> </body> </html>

Como toque final, atualizamos o título do nosso aplicativo na página index.html alterando-o de " React App " para " Claude AI Assistant ".


pacote.json

 { "name": "ai-assistant-claude", "version": "0.1.0", "private": true, ++ "proxy": "http://localhost:5000", "dependencies": {

Por fim, adicionamos uma configuração de proxy ao arquivo package.json e o definimos como http://localhost:5000 . Isso nos ajuda a contornar as limitações do CORS decorrentes do uso de portas diferentes.

Testando o aplicativo AI Assistant

Para começar o teste, primeiro verifique se o aplicativo de back-end (claude-langchain) já está em execução.


Em seguida, altere o diretório para o aplicativo front-end (ai-assistant-claude) e inicie o aplicativo usando o seguinte comando:

 npm start


O aplicativo pode demorar um pouco para ser criado. Assim que estiver pronto, ele abrirá automaticamente em seu navegador em localhost:3000 .


Vamos testar o reconhecimento de contexto e o recurso de pesquisa! Primeiro, vamos perguntar sobre outro videogame cujo lançamento ainda não foi anunciado em 2021 – a sequência do último jogo Yakuza da SEGA . Além disso, perguntaremos se este jogo apresenta o amado personagem Kazuma Kiryu dos jogos anteriores. Para fazer isso, clique no botão " Perguntar ".


Dê-lhe alguns segundos para pensar...



Surpreendentemente, a IA respondeu incorretamente. Yakuza: Like a Dragon é de fato o último jogo da Yakuza, mas estrelado por um protagonista diferente, Ichiban Kasuga. Vamos reformular a pergunta e usar o botão " Perguntar e pesquisar " desta vez.



Agora, a IA demorava mais para decidir se precisava pesquisar na web. Após constatar a necessidade de uma busca na web e encontrar uma resposta satisfatória, devolveu as informações ao cliente/front-end.


Desta vez, ele retorna o título "Like a Dragon Gaiden: The Man Who Erased His Name", que de fato apresenta Kazuma Kiryu como protagonista.


Fascinante, não é? O agente, alimentado por um Large Language Model, prevê quais ações são necessárias dados os recursos disponíveis (pesquisa) e fornece uma resposta satisfatória. O LangChain torna fácil conectar e "encadear" o modelo, prompts (instruções para o modelo) e outros recursos, como agentes e ferramentas.

Conclusão

Esperamos que você tenha gostado de criar este aplicativo e aprender sobre essas tecnologias. Para os interessados em se aprofundar, você pode acessar o projeto concluído para o front-end e back-end .


E se você quiser construir com as tecnologias mais recentes realizadas por nossos parceiros, junte-se a nós no próximo desafio de IA!


*please see lablab.ai for all terms and conditions