Всем привет, я Григорий Тумаков, CTO в Моризо Диджитал.

Недавно рассказал, как мы в компании «потрогали» нейросети для прикладных задач разработки. Но там никаких серьезных выводов сделать не удалось.

Поэтому решил на этом не останавливаться. Если есть инструменты — их надо протестировать на какой-то реальной задаче.

Далее в статье мой опыт сравнения для прикладной задачи трех AI-инструментов: Phind, ChatGPT, Machinet.

Идея и промпт

Создай страницу с использованием JS и html для получения погоды по названию города. На странице должна быть форма заполнения названия города, кнопка "Узнать погоду". Не забудь учесть проблемы с CORS.

Работать это должно таким образом:

  1. Пользователь вводит название города.  

  2. По названию города нужно запросить долготу и широту по этому API

    curl -X POST
    -H "Content-Type: application/json"
    -H "Accept: application/json"
    -H "Authorization: Token {API_TOKEN}"
    -H "X-Secret: {SECRET_TOKEN}"
    -d '[ "{Название города}" ]'
    https://cleaner.dadata.ru/api/v1/clean/address

  1. Далее долготу и широту необходимо подставить в запрос -  https://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={API key}

  2. Вывести результат обращения к API.

Для сравнения я загрузил промпт в каждую из трех нейросетей.

Рассказываю, что получилось.

ChatGPT

*Вот пример кода HTML и JavaScript, который реализует описанную вами функциональность:*

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Получить погоду</title>
</head>
<body>
<h2>Узнать погоду</h2>
<form id="weatherForm">
  <label for="cityInput">Введите название города:</label><br>
  <input type="text" id="cityInput" name="cityInput"><br>
  <button type="submit">Узнать погоду</button>
</form>
<div id="weatherResult"></div>

<script>
document.getElementById('weatherForm').addEventListener('submit', function(event) {
  event.preventDefault(); // Prevent default form submission
 
  var city = document.getElementById('cityInput').value.trim();
  if (city === '') {
    alert('Пожалуйста, введите название города.');
    return;
  }
 
  fetchCoordinates(city)
    .then(function(coordinates) {
      return fetchWeather(coordinates.lat, coordinates.lon);
    })
    .then(function(weather) {
      displayWeather(weather);
    })
    .catch(function(error) {
      console.error('Произошла ошибка:', error);
    });
});

function fetchCoordinates(city) {
  var apiToken = '{API_TOKEN}';
  var secretToken = '{SECRET_TOKEN}';
 
  return fetch('https://cleaner.dadata.ru/api/v1/clean/address', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Accept': 'application/json',
      'Authorization': 'Token ' + apiToken,
      'X-Secret': secretToken
    },
    body: JSON.stringify([city])
  })
  .then(function(response) {
    if (!response.ok) {
      throw new Error('Ошибка при получении координат города.');
    }
    return response.json();
  })
  .then(function(data) {
    var coordinates = data[0].geo_lat && data[0].geo_lon ? { lat: data[0].geo_lat, lon: data[0].geo_lon } : null;
    if (!coordinates) {
      throw new Error('Координаты для указанного города не найдены.');
    }
    return coordinates;
  });
}

function fetchWeather(lat, lon) {
  var apiKey = '{API_KEY}';
 
  return fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${apiKey}`)
    .then(function(response) {
      if (!response.ok) {
        throw new Error('Ошибка при получении данных о погоде.');
      }
      return response.json();
    });
}

function displayWeather(weather) {
  var weatherResultDiv = document.getElementById('weatherResult');
  weatherResultDiv.innerHTML = '';
 
  var cityName = weather.name;
  var weatherDescription = weather.weather && weather.weather[0] ? weather.weather[0].description : '';
  var temperature = weather.main && weather.main.temp ? Math.round(weather.main.temp - 273.15) : '';
 
  var weatherInfo = document.createElement('p');
  weatherInfo.textContent = `Погода в городе ${cityName}: ${weatherDescription}, Температура: ${temperature}°C`;
  weatherResultDiv.appendChild(weatherInfo);
}
</script>
</body>
</html>

*Пожалуйста, обратите внимание, что вы должны заменить `{API_TOKEN}`, `{SECRET_TOKEN}` и `{API_KEY}` на свои реальные значения. Кроме того, помните, что без использования сервера для обхода CORS вы можете столкнуться с проблемами безопасности при попытке выполнить этот код в браузере.* 

Очевидно, что этот код не выведет нам результат, мы получим ошибку CORS. 

Поэтому я попросил придумать решение этой проблемы.

Вот, что предложил ChatGPT

*Чтобы обойти ошибку CORS при обращении к API с клиентской стороны, можно воспользоваться серверной прокси. Например, можно создать простой сервер с использованием Node.js, который будет проксировать запросы к API `cleaner.dadata.ru` и передавать результат обратно на клиент.*

*Вот пример кода для сервера на Node.js:*

const express = require('express');
const fetch = require('node-fetch');
const cors = require('cors');

const app = express();
const PORT = process.env.PORT || 3000;

app.use(cors());

app.post('/getCoordinates', async (req, res) => {
  try {
    const cityName = req.body.cityName;
    const response = await fetch('https://cleaner.dadata.ru/api/v1/clean/address', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': 'Token {API_TOKEN}',
        'X-Secret': '{SECRET_TOKEN}'
      },
      body: JSON.stringify([cityName])
    });

    const data = await response.json();
    const coordinates = data[0].geo_lat && data[0].geo_lon ? { lat: data[0].geo_lat, lon: data[0].geo_lon } : null;

    if (coordinates) {
      res.json(coordinates);
    } else {
      res.status(404).json({ error: 'Coordinates not found for the provided city name' });
    }
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

*Изменения в коде:*

1. *Добавлена библиотека `express` для создания сервера.*

2. *Добавлена библиотека `node-fetch` для выполнения HTTP запросов на сервере.*

3. *Добавлена библиотека `cors` для обработки CORS политики.*

4. *Создан маршрут `/getCoordinates`, который принимает POST запрос с именем города, отправляет запрос к API `cleaner.dadata.ru` через сервер, получает координаты города и отправляет их обратно клиенту.*

*На стороне клиента теперь нужно обратиться к серверу для получения координат города, затем использовать полученные координаты для запроса погоды к API `api.openweathermap.org`.*

<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Weather App</title>
</head>
<body>
  <h1>Weather App</h1>
  <form id="cityForm">
    <label for="cityName">Enter city name:</label>
    <input type="text" id="cityName" name="cityName">
    <button type="submit">Get Weather</button>
  </form>
  <div id="weatherData"></div>

  <script>
    document.getElementById('cityForm').addEventListener('submit', async (event) => {
      event.preventDefault();
     
      const cityName = document.getElementById('cityName').value;
      const response = await fetch('http://localhost:3000/getCoordinates', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json'
        },
        body: JSON.stringify({ cityName })
      });

      const data = await response.json();

      if (data.error) {
        document.getElementById('weatherData').textContent = data.error;
      } else {
        const weatherResponse = await fetch(`https://api.openweathermap.org/data/2.5/weather?lat=${data.lat}&lon=${data.lon}&appid={API key}`);
        const weatherData = await weatherResponse.json();
        document.getElementById('weatherData').textContent = JSON.stringify(weatherData, null, 2);
      }
    });
  </script>
</body>
</html>

*Теперь клиентский JavaScript делает запрос к серверу для получения координат города, затем использует эти координаты для запроса погоды к API `api.openweathermap.org`.*

К сожалению, снова получаю ошибку.

Error [ERR_REQUIRE_ESM]: require() of ES Module .../ai/chat-gpt/node_modules/node-fetch/src/index.js from .../ai/chat-gpt/server.js not supported. Instead change the require of index.js in .../ai/chat-gpt/server.js to a dynamic import() which is available in all CommonJS modules. at Object.<anonymous> (.../ai/chat-gpt/server.js:50:15) { code: 'ERR_REQUIRE_ESM' }

Прошу поправить и получаю новый блок кода, с указанием необходимости установки axios.

const express = require('express');
const axios = require('axios');
const cors = require('cors');

const app = express();
const PORT = process.env.PORT || 3000;

app.use(cors());
app.use(express.json());

app.post('/getCoordinates', async (req, res) => {
  try {
    const cityName = req.body.cityName;
    const response = await axios.post('https://cleaner.dadata.ru/api/v1/clean/address', [cityName], {
      headers: {
        'Content-Type': 'application/json',
        'Accept': 'application/json',
        'Authorization': 'Token {API_TOKEN}',
        'X-Secret': '{SECRET_TOKEN}'
      }
    });

    const data = response.data;
    const coordinates = data[0].geo_lat && data[0].geo_lon ? { lat: data[0].geo_lat, lon: data[0].geo_lon } : null;

    if (coordinates) {
      res.json(coordinates);
    } else {
      res.status(404).json({ error: 'Coordinates not found for the provided city name' });
    }
  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: 'Internal Server Error' });
  }
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

И наконец получаю нужный результат, приложение работает

Попрошу отформатировать полученный ответ от API.

*Выведи красиво ответ от API, чтобы было понятно какая сейчас погода, скорость ветра и так далее*

Phind

Загружаю промпт и сразу вижу различия в результате. Phind предпочел разделить html и js код в разные файлы, что радует.

К сожалению, не получаю работающую программу с первого запроса, поэтому прошу добавить веб-сервер для обхода проблемы с CORS. Вижу, что Phind дает более развернутые ответы и предлагает несколько вариантов.

Пробую сначала первый вариант. Спрашиваю, как его использовать, и получаю достаточно подробную инструкцию. 

После изменения кода пробую запустить сервер, но получаю уже знакомую ошибку.

```

Error [ERR_REQUIRE_ESM]: require() of ES Module .../ai/chat-gpt/node_modules/node-fetch/src/index.js from .../ai/chat-gpt/server.js not supported. Instead change the require of index.js in .../ai/chat-gpt/server.js to a dynamic import() which is available in all CommonJS modules. at Object.<anonymous> (.../ai/chat-gpt/server.js:50:15) { code: 'ERR_REQUIRE_ESM' }

```

Прошу исправить ошибку и получаю ответ, который значительно лучше по сравнению с ChatGPT, он подробный и в нем объясняется проблема.

Пробую использовать предложенное решение — все работает.

Прошу красиво вывести ответ от апи, чтобы было понятно, какая погода, ветер и т.д.

Правлю код и пробую получить данные.

Теперь тестирую второй предложенный вариант. Применяю изменения, запускаю.

Но выходит ошибка — прошу ее исправить.

Снова получаю развернутый ответ с объяснением ошибки и несколькими решениями, но теперь уже знаю, что первое решение поможет, поэтому не продолжаю.

Machinet

Загружаю промпт, получаю ответ:

*Хорошо, я могу помочь вам создать страницу для получения погоды по названию города с использованием JavaScript и HTML. Для начала, вам понадобится создать HTML-разметку для страницы. Вот пример кода:*

<!DOCTYPE html>
<html>
<head>
  <title>Получение погоды</title>
  <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
  <h1>Получение погоды по названию города</h1>
  <form id="weatherForm">
    <label for="cityInput">Введите название города:</label>
    <input type="text" id="cityInput" required>
    <button type="submit">Узнать погоду</button>
  </form>
  <div id="weatherResult"></div>

  <script>
    // Обработчик события отправки формы
    $("#weatherForm").submit(function(event) {
      event.preventDefault(); // Предотвращаем отправку формы

      var city = $("#cityInput").val(); // Получаем значение поля ввода

      // Запрос для получения долготы и широты
      $.ajax({
        url: "https://cleaner.dadata.ru/api/v1/clean/address",
        type: "POST",
        headers: {
          "Content-Type": "application/json",
          "Accept": "application/json",
          "Authorization": "Token {API_TOKEN}",
          "X-Secret": "{SECRET_TOKEN}"
        },
        data: JSON.stringify([city]),
        success: function(response) {
          var latitude = response[0].geo_lat;
          var longitude = response[0].geo_lon;

          // Запрос для получения погоды
          $.ajax({
            url: "https://api.openweathermap.org/data/2.5/weather",
            type: "GET",
            data: {
              lat: latitude,
              lon: longitude,
              appid: "{API key}"
            },
            success: function(weatherData) {
              // Выводим результат обращения к API
              $("#weatherResult").html("Текущая погода в городе " + city + ": " + weatherData.weather[0].description);
            },
            error: function() {
              $("#weatherResult").html("Ошибка при получении погоды");
            }
          });
        },
        error: function() {
          $("#weatherResult").html("Ошибка при получении координат города");
        }
      });
    });
  </script>
</body>
</html>

*В этом примере мы используем jQuery для упрощения работы с AJAX-запросами. При отправке формы, мы сначала отправляем запрос на получение долготы и широты города с помощью API от Dadata, а затем используем полученные координаты для запроса погоды с помощью API от OpenWeatherMap. Результат выводится в элементе с id "weatherResult".*

*Обратите внимание, что вам нужно будет заменить {API_TOKEN}, {SECRET_TOKEN} и {API key} на соответствующие значения, полученные при регистрации на соответствующих сервисах.*

Пробую запустить код и выполнить запрос. Снова получаю ошибку CORS, прошу исправить, используя тот же текст, что и в предыдущих нейросетях — и получаю интересный ответ.

Выбираю второй подход.

Пробую запустить сервер и получаю ошибку.

app.use(corsAnywhere({
        ^
TypeError: corsAnywhere is not a function

Прошу исправить.

После исправлений сервер действительно запустился. Но при выполнении запроса получаю новую ошибку, снова прошу ее исправить.

Применяю изменения и пробую выполнить запрос еще раз, но снова получаю ошибку.

Пробую дать больше информации об ошибке.

К сожалению, не получаю никакого решения, только рекомендации. Здесь нет смысла дальше продолжать.

Результаты

Хуже всех с поставленной задачей справилась Machinet, ей не удалось выдать работающего решения. Возможно, на более простых задачах она сможет помочь.

Если же выбирать из ChatGPT и Phind, то я бы предпочел Phind, поскольку она давала более развернутые ответы и предлагала больше вариантов решения задач.

Комментарии (2)


  1. vikarti
    15.04.2024 14:48
    +1

    Чисто ради примера :)

    Kagi Search

    fastgpt (kagi.com/fastgpt)

    Kagi Search - FastGPT
    Kagi Search - FastGPT

    Assistant (Expert, Resarch mode)

    Kagi Search - Research Assistant, Expert
    Kagi Search - Research Assistant, Expert

    Kagi Search - Research Assistant, Fast

    Kagi Search - Research Assistant , Fast
    Kagi Search - Research Assistant , Fast

    Kagi Search, Code Assistant

    Kagi Search - Code Assistant
    Kagi Search - Code Assistant


    FastGPT доступен всем пользователям Kagi Search,

    Fast Mode Research Assistant'а - всем(но пока assistant в бете только для тех у кого топовый план), заявлено что он сейчас на Claude 3 Haiku

    Expert mode - заявлено что на Claude 3 Sonnet и доступен только с топовым планом.

    Одно из преимуществ - это вообще то поисковая система весьма интересная и со своими особенностями, ИИ-часть там опциональна но удобна. И зачем платить за подписку ChatGPT Plus если можно платить Kagi ? :) (ну кроме картинок) (зачем вообще платить за поиск - у них подробно расписано - https://help.kagi.com/kagi/why-kagi/why-pay-for-search.html )


  1. pkokoshnikov
    15.04.2024 14:48
    +1

    Очень сложно добиться нужного результата. Да какой то результат будет, но почти наверняка не, то что вы хотели. Постоянно пробую разные чаты, есть подписка openai. Удачных кейсов по разработке у себя могу по пальцам пересчитать. Чаще быстрее самому написать