#2 crewAI: prepare-se para a Rebelião das Máquinas!
TL;DR: Neste segundo artigo da série, vamos aprofundar nosso projeto no crewAI, focado na criação de uma equipe responsável por elaborar um plano de viagem. Abordaremos a criação de novas ferramentas, agentes e tarefas mais complexas. Se você ainda não leu o primeiro artigo, recomendo que o faça.
Após uma discussão enriquecedora no grupo de WhatsApp do Canal Sandeco e uma revisão da aula sobre colaboração multi-agentes do curso “Multi AI Agent System with crewAI” do João Moura, percebi que a estrutura hierárquica dos agentes será interessante apenas quando todos os agentes estiverem criados. Portanto, para seguir em frente, adotarei a abordagem sequencial. Segundo a documentação do crewAI, essa abordagem executa as tarefas na ordem predefinida na lista, onde a saída de uma tarefa serve como contexto para a próxima. E o desenho mais adequado, para agora, seria a ordem das tarefas, e não a hierarquia dos agentes. Neste artigo, seguiremos a sequência conforme a imagem abaixo, e nos próximos artigos desta série, continuaremos a evoluir esse processo.
Antes de prosseguirmos com a criação dos novos itens no projeto crewAI, gostaria de informar que adaptei o código do serviço REST criado no artigo anterior. Simplifiquei-o para evitar que os agentes se confundissem com informações que ainda não eram necessárias. A principal mudança é que o serviço agora retorna apenas o nome do cliente e o itinerário desejado. Veja como ficou o código:
# Instalar Flask e pyngrok
!pip install Flask Flask-SQLAlchemy pyngrok
# Importar as bibliotecas necessárias
from flask import Flask, request, jsonify
from flask_sqlalchemy import SQLAlchemy
from pyngrok import ngrok
from sqlalchemy.dialects.postgresql import ARRAY
from datetime import date
# Autenticar ngrok com seu token
!ngrok authtoken <put_your_ngrok_key_here>
# Inicializar a aplicação Flask
app = Flask(__name__)
# Configurar a URL do banco de dados SQLite
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///test.db'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
# Inicializar o banco de dados
db = SQLAlchemy(app)
# Definir o modelo de dados Trip
class Trip(db.Model):
id = db.Column(db.Integer, primary_key=True)
client_name = db.Column(db.String(80), nullable=False)
itinerary = db.Column(db.PickleType, nullable=True) # Usando PickleType para armazenar arrays
# Criar o banco de dados e a tabela com o contexto de aplicação
with app.app_context():
db.create_all()
# Adicionar registros iniciais
if not Trip.query.first():
trip1 = Trip(
client_name="Wonder Woman",
itinerary=["Lisboa", "Valladolid", "Lourdes", "Nice", "Marseille", "Saragoça", "Madrid", "Lisboa"]
)
db.session.add(trip1)
db.session.commit()
# Definir uma rota para retornar uma viagem específica
@app.route('/trips/<int:id>', methods=['GET'])
def get_trip(id):
trip = Trip.query.get_or_404(id)
result = {
"id": trip.id,
"client_name": trip.client_name,
"itinerary": trip.itinerary
}
return jsonify(result), 200
# Definir uma rota simples
@app.route('/')
def hello_world():
return "Hello, World!"
# Rodar a aplicação Flask
if __name__ == '__main__':
# Configurar o ngrok para criar um túnel para a aplicação Flask
url = ngrok.connect(5000)
print('Public URL:', url)
app.run(port=5000)
Depois de toda essa introdução, vamos ao que interessa! A próxima etapa é criar um novo agente, chamado Agente de Roteamento, que será responsável por calcular a distância entre cidades e o tempo de viagem de carro entre elas. Para isso, desenvolveremos uma nova ferramenta personalizada que acessará o OpenRouteService para realizar esses cálculos.
O primeiro passo é criar sua chave no OpenRouteService e configurá-la no código, de maneira semelhante ao que fizemos com o Groq.
# Conexão ao OpenRoute Service
ors = openrouteservice.Client(key="<cole_sua_key_aqui>")
O segundo passo foi criar uma ferramenta para calcular a distância entre as cidades do itinerário e o tempo de viagem entre elas. Note que a ferramenta recebe o itinerário do contexto da atividade executada pelo Agente de Turismo, que obteve esse itinerário na API de um serviço REST. Nos trechos de código onde você vê “ors”, é o momento em que a ferramenta está chamando o OpenRouteService. Ressalto que o objetivo aqui não é se tornar um especialista em geoprocessamento 🌍, mas sim aprender como criar ferramentas ⚒️ customizadas.
# Função auxiliar que será usada pela ferramenta.
def get_city_coordinates(city_names):
coordinates = {}
for city in city_names:
geocode = ors.pelias_search(text=city)
if geocode['features']:
coordinates[city] = geocode['features'][0]['geometry']['coordinates']
return coordinates
@tool("Buscar a distância entre as cidades seguindo a ordem do itinerário.")
def route_tool(itinerary: list) -> list:
"""
Uma ferramenta para buscar a distância em km entre as cidades do itinerário.
Essa função faz uma requisição à API do OpenRouteService para recuperar a rotas entre duas cidades.
Em caso de sucesso, a função retorna uma lista composta por objetos json no formato:
"{'from': from_city, 'to': to_city, 'distance_km': distance_km, 'duration_hours': duration_hours}”.
Se a solicitação falhar por algum motivo, como problemas de conexão ou erros de API,
a função retornará uma mensagem de erro indicando o problema encontrado.
"""
# Obtendo as coordenadas das cidades
city_coordinates = get_city_coordinates(itinerary)
# Calculando a distância e duração para cada par de cidades adjacentes
results = []
num_cities = len(itinerary)
for i in range(num_cities - 1):
from_city = itinerary[i]
to_city = itinerary[i + 1]
# Obtendo as coordenadas das cidades adjacentes
from_coordinates = city_coordinates[from_city]
to_coordinates = city_coordinates[to_city]
# Fazendo a solicitação para calcular a distância e duração entre as cidades adjacentes
route = ors.directions(
coordinates=[from_coordinates, to_coordinates],
profile='driving-car',
format='geojson'
)
# Extraindo a distância e duração da rota
distance_km = route['features'][0]['properties']['segments'][0]['distance'] / 1000 # Convertendo para quilômetros
duration_hours = route['features'][0]['properties']['segments'][0]['duration'] / 3600 # Convertendo para horas
# Adicionando os resultados à lista
result = {
"from": from_city,
"to": to_city,
"distance_km": distance_km,
"duration_hours": duration_hours
}
results.append(result)
return results
O terceiro passo é criar o agente em si. Inicialmente, eu havia pensado no objetivo (goal) abaixo para ele, mas depois o tornei mais especializado, já que também limitei o retorno do serviço. No entanto, não se desiluda: assim que todos os agentes estiverem prontos com suas respectivas tarefas e ferramentas, vou tentar estender o objetivo dele.
Calcular a melhor rota de ida e volta entre a origem e o destino final da viagem, considerando o limite de distância a percorrer por dia e as cidades de interesse.
O código do Agente de Roteamento ficou assim:
route_agent = Agent(
role="Agente de Roteamento",
goal="Calcular apenas a distância e a duração entre os itens adjacentes do itinerário.",
backstory="Com uma vasta experiência em mapeamento e navegação, o agente "
"de roteamento utiliza algoritmos avançados para garantir que "
"os clientes tenham a rota mais eficiente e segura para sua viagem.",
allow_delegation=False,
tools=[route_tool],
verbose=True,
llm=llama3
Vale ressaltar que também alterei o allow_delegation para false do Agente de Viagem.
O quarto passo foi criar duas tarefas: uma para calcular a distância e o tempo de viagem entre as cidades, que será executada pelo Agente de Roteamento, e outra para consolidar os dados da viagem, onde o processo retorna para o Agente de Turismo. É importante destacar que o contexto da atividade de calcular a distância e o tempo de viagem é fornecido pela primeira tarefa executada pelo Agente de Turismo, que busca o itinerário de uma determinada viagem.
calculate_distance_and_travel_time_task = Task(
description="Descobrir a distância e a duração da viagem de carro entre as cidades.",
expected_output="Lista com a distância e a duração da viagem de carro entre as cidades do itinerário.",
tools = [route_tool],
agent = route_agent,
context=[load_trip_task]
)
consolidate_trip = Task (
description="Consolidar os dados da viagem do cliente.",
expected_output="Dados da viagem em estilo markdown.",
agent = trip_agent
)
O passo final é adicionar este novo agente e as novas tarefas à equipe do crewAI.
# Crew
trip_crew = Crew(
agents=[trip_agent, route_agent],
tasks=[load_trip_task, calculate_distance_and_travel_time_task, consolidate_trip],
process=Process.sequential,
verbose=True
)
O código final do serviço e do crewAI está disponível no repositório https://github.com/lusabo/crewai. Os arquivos relevantes para esta série são: crewai_service.ipynb e crewai_trip.ipynb.
Aqui tá o console final depois de rodar essa Crew!
[DEBUG]: == Working Agent: Agente de Viagem
[INFO]: == Starting Task: Analisar a requisição cujo id é 1 para extrair os detalhes a fim de montar o melhor roteiro de viagem.
> Entering new CrewAgentExecutor chain...
Thought: I need to access the trip request data to plan the best route.
Action: Buscar dados da viagem via API
Action Input: {'trip_id': '1'}
{'client_name': 'Wonder Woman', 'id': 1, 'itinerary': ['Lisboa', 'Valladolid', 'Lourdes', 'Nice', 'Marseille', 'Saragoça', 'Madrid', 'Lisboa']}
Thought: I now know the final answer
Final Answer: {'client_name': 'Wonder Woman', 'id': 1, 'itinerary': ['Lisboa', 'Valladolid', 'Lourdes', 'Nice', 'Marseille', 'Saragoça', 'Madrid', 'Lisboa']}
> Finished chain.
[DEBUG]: == [Agente de Viagem] Task output: {'client_name': 'Wonder Woman', 'id': 1, 'itinerary': ['Lisboa', 'Valladolid', 'Lourdes', 'Nice', 'Marseille', 'Saragoça', 'Madrid', 'Lisboa']}
[DEBUG]: == Working Agent: Agente de Roteamento
[INFO]: == Starting Task: Descobrir a distância e a duração da viagem de carro entre as cidades.
> Entering new CrewAgentExecutor chain...
I need to calculate the distance and duration between each pair of adjacent cities in the itinerary.
Action: Buscar a distância entre as cidades seguindo a ordem do itinerário.
Action Input: {"itinerary": ["Lisboa", "Valladolid", "Lourdes", "Nice", "Marseille", "Saragoça", "Madrid", "Lisboa"]}
[{'from': 'Lisboa', 'to': 'Valladolid', 'distance_km': 583.6294, 'duration_hours': 5.549777777777778}, {'from': 'Valladolid', 'to': 'Lourdes', 'distance_km': 544.327, 'duration_hours': 5.3885}, {'from': 'Lourdes', 'to': 'Nice', 'distance_km': 728.1339, 'duration_hours': 7.046583333333333}, {'from': 'Nice', 'to': 'Marseille', 'distance_km': 202.574, 'duration_hours': 2.1871388888888887}, {'from': 'Marseille', 'to': 'Saragoça', 'distance_km': 786.036, 'duration_hours': 7.627972222222223}, {'from': 'Saragoça', 'to': 'Madrid', 'distance_km': 307.5577, 'duration_hours': 3.171111111111111}, {'from': 'Madrid', 'to': 'Lisboa', 'distance_km': 633.9265, 'duration_hours': 6.0145277777777775}]
Thought:
I have the distances and durations between each pair of adjacent cities in the itinerary.
Final Answer:
[{'from': 'Lisboa', 'to': 'Valladolid', 'distance_km': 583.6294, 'duration_hours': 5.549777777777778},
{'from': 'Valladolid', 'to': 'Lourdes', 'distance_km': 544.327, 'duration_hours': 5.3885},
{'from': 'Lourdes', 'to': 'Nice', 'distance_km': 728.1339, 'duration_hours': 7.046583333333333},
{'from': 'Nice', 'to': 'Marseille', 'distance_km': 202.574, 'duration_hours': 2.1871388888888887},
{'from': 'Marseille', 'to': 'Saragoça', 'distance_km': 786.036, 'duration_hours': 7.627972222222223},
{'from': 'Saragoça', 'to': 'Madrid', 'distance_km': 307.5577, 'duration_hours': 3.171111111111111},
{'from': 'Madrid', 'to': 'Lisboa', 'distance_km': 633.9265, 'duration_hours': 6.0145277777777775}]
> Finished chain.
[DEBUG]: == [Agente de Roteamento] Task output: [{'from': 'Lisboa', 'to': 'Valladolid', 'distance_km': 583.6294, 'duration_hours': 5.549777777777778},
{'from': 'Valladolid', 'to': 'Lourdes', 'distance_km': 544.327, 'duration_hours': 5.3885},
{'from': 'Lourdes', 'to': 'Nice', 'distance_km': 728.1339, 'duration_hours': 7.046583333333333},
{'from': 'Nice', 'to': 'Marseille', 'distance_km': 202.574, 'duration_hours': 2.1871388888888887},
{'from': 'Marseille', 'to': 'Saragoça', 'distance_km': 786.036, 'duration_hours': 7.627972222222223},
{'from': 'Saragoça', 'to': 'Madrid', 'distance_km': 307.5577, 'duration_hours': 3.171111111111111},
{'from': 'Madrid', 'to': 'Lisboa', 'distance_km': 633.9265, 'duration_hours': 6.0145277777777775}]
[DEBUG]: == Working Agent: Agente de Viagem
[INFO]: == Starting Task: Consolidar os dados da viagem do cliente.
> Entering new CrewAgentExecutor chain...
I now can give a great answer!
Final Answer:
**Travel Itinerary**
### Leg 1: Lisboa to Valladolid
* Distance: 583.63 km
* Duration: 5 hours 33 minutes
### Leg 2: Valladolid to Lourdes
* Distance: 544.33 km
* Duration: 5 hours 23 minutes
### Leg 3: Lourdes to Nice
* Distance: 728.13 km
* Duration: 7 hours 2 minutes
### Leg 4: Nice to Marseille
* Distance: 202.57 km
* Duration: 2 hours 11 minutes
### Leg 5: Marseille to Saragoça
* Distance: 786.04 km
* Duration: 7 hours 37 minutes
### Leg 6: Saragoça to Madrid
* Distance: 307.56 km
* Duration: 3 hours 10 minutes
### Leg 7: Madrid to Lisboa
* Distance: 633.93 km
* Duration: 6 hours 1 minute
**Total Distance:** 3,485.19 km
**Total Duration:** 36 hours 19 minutes
> Finished chain.
[DEBUG]: == [Agente de Viagem] Task output: **Travel Itinerary**
### Leg 1: Lisboa to Valladolid
* Distance: 583.63 km
* Duration: 5 hours 33 minutes
### Leg 2: Valladolid to Lourdes
* Distance: 544.33 km
* Duration: 5 hours 23 minutes
### Leg 3: Lourdes to Nice
* Distance: 728.13 km
* Duration: 7 hours 2 minutes
### Leg 4: Nice to Marseille
* Distance: 202.57 km
* Duration: 2 hours 11 minutes
### Leg 5: Marseille to Saragoça
* Distance: 786.04 km
* Duration: 7 hours 37 minutes
### Leg 6: Saragoça to Madrid
* Distance: 307.56 km
* Duration: 3 hours 10 minutes
### Leg 7: Madrid to Lisboa
* Distance: 633.93 km
* Duration: 6 hours 1 minute
**Total Distance:** 3,485.19 km
**Total Duration:** 36 hours 19 minutes
**Travel Itinerary**
### Leg 1: Lisboa to Valladolid
* Distance: 583.63 km
* Duration: 5 hours 33 minutes
### Leg 2: Valladolid to Lourdes
* Distance: 544.33 km
* Duration: 5 hours 23 minutes
### Leg 3: Lourdes to Nice
* Distance: 728.13 km
* Duration: 7 hours 2 minutes
### Leg 4: Nice to Marseille
* Distance: 202.57 km
* Duration: 2 hours 11 minutes
### Leg 5: Marseille to Saragoça
* Distance: 786.04 km
* Duration: 7 hours 37 minutes
### Leg 6: Saragoça to Madrid
* Distance: 307.56 km
* Duration: 3 hours 10 minutes
### Leg 7: Madrid to Lisboa
* Distance: 633.93 km
* Duration: 6 hours 1 minute
**Total Distance:** 3,485.19 km
**Total Duration:** 36 hours 19 minutes
Chegamos ao fim de mais um artigo! Espero que este conteúdo tenha sido útil e inspirador para você. Se você gostou do que leu, por favor, deixe um like e compartilhe com outros que possam se beneficiar.