Pull to refresh
8
3
Send message

Родион, спасибо большое за отзыв и вопрос:)

Мы не сравнивали все эти модели, поэтому можем предположить, как бы они себя проявили.

1. OpenChat (7 млрд, open source, https://huggingface.co/openchat/openchat_3.5) — модель со значительно меньшим количеством параметров, чем YandexGPT (100 млрд) и GigaChat (29 млрд). Кроме того, она позиционируется как англоязычная, а не многоязычная модель.

Доступ к API https://openchat.team/ недоступен.

2. Command R+ имеет достаточно параметров для адекватного сопоставления. Однако в перечне языков, на которых валидировалась модель, отсутствует русский (104 млрд, https://huggingface.co/CohereForAI/c4ai-command-r-plus). Тем не менее, через Hugging Face API можно пообщаться с ней (https://huggingface.co/spaces/CohereForAI/c4ai-command-r-plus).

Привожу примеры пересказа двух художественных текстов и оценки, которые бы получила модель в эксперименте:

«Собачье сердце»: «В рассказе описывается история бездомной собаки, которая была обварена кипятком поваром столовой. Собака страдает от боли и голода, но неожиданно встречает богатого человека, который подкармливает ее колбасой и забирает с собой. Собака благодарна и предана своему спасителю, несмотря на то, что тот живет в роскоши и имеет странные привычки. По пути к дому богатого человека собака выражает свою любовь и преданность, защищая своего нового хозяина от других собак и котов. В конце они приходят в дом, где богатый человек живет с другими людьми, и собака узнает, что в квартире будут проводиться ремонтные работы».

Оценки, которые бы получила модель: фактология = 1 (в доме не будут проводиться ремонтные работ, собака не защищала хозяина от собак и котов и др.), полнота = 8 (неверно расставлены содержательные акценты, не упомянут диалог со швейцаром и имена действующих лиц), стиль 7 (снижение на 2 балла за то, что модель нарушила инструкцию составить пересказ на 3-4 предложения, 1 балл за «история бездомной собаки, которая была обварена кипятком»).

«С неба упали три яблока»: «Воске, младшая дочь Севоянц Капитона, готовится к смерти, выполняя различные ритуалы и оставляя инструкции для своих соседей. Она вспоминает свою жизнь, включая смерть своей сестры Татевик за четыре дня до их запланированной свадьбы, и свое последующее замужество за Капитоном. Воске также вспоминает сон, который она видела перед свадьбой, и совет, который дал ей толкователь снов, чтобы она никогда не стригла свои волосы, так как они являются ее защитой от проклятия».

Оценки, которые бы получила модель: фактология = 1 (модель принимает Анатолию за её мать Воске), полнота = 7 (неверно расставлены содержательные акценты, не говорится о сути семейного проклятья, о кровотечении), стиль 8 (снижение за «готовится к смерти, выполняя различные ритуалы и оставляя инструкции для своих соседей», «смерть своей сестры Татевик за четыре дня до их запланированной свадьбы» как будто женятся Воске и Татевик)

 

3. llama 3  — ситуация аналогичная: количество параметров сопоставимое (70 млрд), однако на русском не валидировалась. Привожу примеры пересказа двух художественных текстов и оценки, которые бы получила модель в эксперименте:

«Собачье сердце»: «Вьюга свирепствует в переулке, и раненый бродячий пёс, Шарик, лежит в подворотне, умирая от боли и голода. Он проклинает повара, который облил его кипятком, и мечтает о еде. Внезапно появляется господин в шубе, который покупает колбасу в магазине. Пёс умоляет его о помощи, и господин дает ему кусок колбасы. Шарик выражает свою благодарность и преданность, и господин предлагает ему пойти за ним. Они идут по Пречистенке, и Шарик продолжает выражать свою преданность, несмотря на боль в боку. Они подходят к дому, и господин разговаривает со швейцаром, узнавая о новых жильцах в квартире. Шарик следует за господином, готовый идти за ним куда угодно».

Оценки, которые бы получила модель: фактология = 1 (в квартире профессора нет новых жильцов, Шариком пса нарёк профессор), полнота = 9 (суть вселения в дом новых жильцов отражена неверно), стиль 5 (снижение на 2 балла за то, что модель нарушила инструкцию составить пересказ на 3-4 предложения, по 1 баллу за «Вьюга свирепствует в переулке», «Пёс умоляет его о помощи», «Шарик продолжает выражать свою преданность, несмотря на боль в боку»)

«С неба упали три яблока»:
«Here is a brief summary of the text in 3-4 sentences: Sevoyants Anatolia, a 58-year-old woman, is preparing to die, carefully attending to her household chores and arranging her funeral attire. She has been bleeding internally for two days and knows that her illness is incurable. Anatolia's life has been marked by tragedy, including the death of her mother at a young age and the sudden passing of her older sister on the eve of her wedding. The family's history is intertwined with a curse that has haunted them for 18 years, and Anatolia's own fate seems to be tied to her inability to fulfill her role as a mother.»

Оценки, которые бы получила модель: фактология = 1 (путает Анатолию с её матерью Воске), полнота = 8 (неверно интерпретирована семейная история и семейное проклятие), стиль 0 (ответ на английском языке).

 

4.  Claude 3 Haiku — проприетарная модель, ориентированная на английский язык. Её главным позиционируемым достоинством является скорость в обработке данных. К сожалению, модель не предоставляет доступ к своему API в нашем регионе: https://www.anthropic.com/claude-ai-locations.

 

Мы работали по Agile, но дело было не только в часто меняющихся требованиях, а в том, что помимо них, проект был с фиксированной длительностью и стоимостью. Если бы не этот фактор, разумеется, Agile решил бы многие проблемы.

Спасибо, что делитесь своим опытом. По части версионности все верно. Сопровождение обновленных контрактов это не технический долг. В статье мы обозначили немного другую проблему, которая возникает при разделении одной OpenAPI спецификации между несколькими сервисами (или контроллерами).


Для понимания ситуации ниже приведем листинг одного из контроллеров. В нем используются только три метода из спецификации OpenAPI (Swagger), а оставшаяся часть этой спецификации используется в других сервисах.

Поэтому нам необходимо пометить неиспользуемую реализацию заглушек атрибутом [NonAction], чтобы закрыть к ним доступ для этого контроллера (сервиса).

/// <summary>
/// API для скачивания файлов
/// </summary>
[ApiVersion("3.0")]
[Route("api/v{version:apiVersion}/[controller]")]
[ApiController]
public class DownloadController : FileApiControllerBase
{
    private static readonly string _defaultBucket = Guid.Empty.ToString("N");
    private readonly IStorageClient _storageClient;
    private readonly IGrpcFileClientWrapper _grpcClientWrapper;

    public DownloadController(IStorageClient storageClient, IGrpcFileClientWrapper grpcClientWrapper)
    {
        _storageClient = storageClient;
        _grpcClientWrapper = grpcClientWrapper;
    }

    /// <summary>
    /// Скачать файл из локального хранилища
    /// </summary>
    /// <param name="docId">Идентификатор файла</param>
    /// <param name="ext">Расширение файла</param>
    /// <param name="bucket">Контейнер файла</param>
    /// <returns>Файл для скачивания</returns>
    [MapToApiVersion("3.0")]
    [HttpGet("{docId}.{ext}")]
    [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(FileStreamResult))]
    [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(Error))]
    public override async Task<IActionResult> DownloadFile(string docId, string ext, string bucket = "")
    {
         return  await Task.FromResult(RedirectToAction("DownloadFileFromBucket", new { docId = docId, ext = ext, bucket = bucket }));
    }

    /// <summary>
    /// Скачать файл из локального хранилища
    /// </summary>
    /// <param name="docId">Идентификатор файла</param>
    /// <param name="ext">Расширение файла</param>
    /// <param name="bucket">Контейнер файла</param>
    /// <returns>Файл для скачивания</returns>
    [MapToApiVersion("3.0")]
    [HttpGet("{bucket}/{docId}.{ext}")]
    [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(FileStreamResult))]
    [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(Error))]
    public override  async Task<IActionResult> Doc(string docId, string ext, string bucket = "")
    {
        return await Task.FromResult(RedirectToAction("DownloadFileFromBucket", new { docId = docId, ext = ext, bucket = bucket }));
    }

    /// <summary>
    /// Скачать файл из локального хранилища
    /// </summary>
    /// <param name="docId">Идентификатор файла</param>
    /// <param name="ext">Расширение файла</param>
    /// <param name="bucket">Контейнер файла</param>
    /// <returns>Файл для скачивания</returns>
    [MapToApiVersion("3.0")]
    [HttpGet("doc")]
    [ProducesResponseType(StatusCodes.Status200OK, Type = typeof(FileStreamResult))]
    [ProducesResponseType(StatusCodes.Status500InternalServerError, Type = typeof(Error))]

    //  [ResponseCache(Location = ResponseCacheLocation.Any, Duration = 16800, VaryByQueryKeys = new string[] { "bucket", "docId" })]
    public override async Task<IActionResult> DownloadFileFromBucket(string docId, string? bucket = "", string ext="")
    {
        if (string.IsNullOrEmpty(bucket))
            bucket = _defaultBucket;
        if (docId.Contains('-'))
            docId = docId.Replace("-", string.Empty);

        var prefix = new Uri(Path.GetDirectoryName(Assembly.GetExecutingAssembly().GetName().CodeBase)).LocalPath;
        var path = Path.Combine(prefix, "doc", bucket, docId);
        Task task = null;
        Stream stream = new MemoryStream();

        if (System.IO.File.Exists(path))
        {
            stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read);
        }
        else
        {
            var tryLoad = false;
            try
            {
                var byteArray = await _storageClient.DownloadFileAsync(docId);
                stream.Write(byteArray, 0, byteArray.Length);
                stream.Seek(0, SeekOrigin.Begin);
                tryLoad = byteArray.Length > 0;
#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
                Task.Run(() => {
                    try
                    {
                        var dir = Path.GetDirectoryName(path);
                        if (!Directory.Exists(dir))
                        {
                            Directory.CreateDirectory(dir);
                            System.IO.File.WriteAllBytes(path, byteArray);
                        }
                    }
                    catch (Exception ex)
                    {
                        //todo log
                    }
                });
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
            }
            catch (Exception ex)
            {
                //todo log
            }
            if (!tryLoad)
            {
                //check tempStorage
            }
            if (stream.Length == 0)
            {
                stream.Close();
                System.IO.File.Delete(path);
            }
        }

#pragma warning disable CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
        _grpcClientWrapper.RegisterDownloadFileAsync(docId, this.GetHeader("UserId"));
#pragma warning restore CS4014 // Because this call is not awaited, execution of the current method continues before the call is completed
      
        var contentType =string.IsNullOrEmpty(ext)? "application/octet-stream" : ext.Contains('/')? ext: GetMimeFromExt(ext);
        //todo replace stubs to real content type
        return File(stream, contentType);
    }

    private static string GetMimeFromExt(string ext)
    {
        //todo switch with predefinded mimes
        return $"application/{ext}";
    }

    [NonAction]
    public override Task<ActionResult<string>> UploadFile(IFormFile body = null)
    {
        throw new NotImplementedException();
    }
    [NonAction]
    public override Task<ActionResult<ICollection<FileItem>>> List([FromQuery] int? page = 0, [FromQuery] int? pageLength = 10, [FromQuery] bool? isMine = false)
    {
        throw new NotImplementedException();
    }
}

То есть при выпуске новой версии, в случае разделения реализации (implementation) между разными контроллерами или сервисами API, нам требуется исключить неиспользуемые определения спецификации (paths), которые предоставляются кодогенерацией, для каждого конкретного случая частичного использования. Это и определяет наш технический долг, который не возникает при посервисной реализации спецификаций OpenAPI.

Однако посервисная реализация OpenAPI (как вариант решения) нам тоже не очень подходит. Гранулярность контрактов в итоге может оказаться слишком малой (множество мелких спецификаций), контексты предметной области или определенных бизнес-процессов будут размазаны по нескольким спецификациям, сопровождение и проектирование затруднено.

Собственно, в этом и проблема проявлялась у нас. При сборке целевого проекта API, изменения контракта в yaml файле не применяются на генерируемые файлы в подключаемой библиотеке с артефактами кодогенерации. Причем эта проблема плавающая (не всегда проявляется).

Спасибо за замечания! Статья является частью цикла по Design API First и рассматривать ее как отдельную инструкцию, по которой можно с нуля спроектировать API, не нужно. Все-таки для этого необходимо некоторое погружение в предметную область. Сущности предметной области, о которых идет речь в п.2, представляют собой некоторую концептуальную высокоуровневую модель, с которой начинается проектирование. Примеры таких сущностей приведены в разделе «Выявление общих компонентов и объектов автоматизации». П.6 относится больше к технической проработке (он не является обязательным, если сущностей не очень много), и он не всегда связан напрямую с п. 2.

Для того чтобы API было независимым от БТ (это идеальный вариант, к которому нужно стремиться), нужно перелопатить достаточно большое количество БТ, только после этого API становится устойчивым к изменению БТ. То есть такая зависимость так или иначе присутствует, но не всегда носит линейный характер. Проектирование API на практике не такой простой процесс, как может показаться на первый взгляд. Для этого нужен опыт, и постоянное возвращение к предыдущим шагам, чтобы проверить, все ли требования увязываются между собой.

Статья вводная и дает общее представление тем, кто интересуется темой недавно или ищет, с чего начать погружение. В следующих частях мы подробнее описываем процесс проектирования и разработки по принципам Design Api First — вторая статья вышла сегодня.

При Code First проектирование Api в руках разработчиков backend и генераторов спецификации их программного стека. В зависимости от конкретного генератора спецификация будет отличаться. Если backend на разных языках или команды используют разные генераторы спецификации, результат неидентичен и использовать его в общем случае неудобно. Кроме того, если от этой спецификации зависит frontend или команда автотестирования Api — они ждут, пока программисты backend дадут отмашку.

При Design API First спецификация пишется универсальной (в соответствии с https://spec.openapis.org/, а не как результат работы генераторов). Ее можно, например, использовать параллельно и на frontend и на backend, начинать писать на ее основе контрактные тесты раньше, использовать как первоначальный источник истины/изменений при взаимодействии между несколькими командами и так далее.

Проект под NDA, поэтому финальной версией поделиться не можем.

Строк кода действительно не хватало, сейчас добавили)

Конечно использование плагинов для IDE редактора тоже можно использовать для сокращения объема кода, но достаточно посмотреть на популярность emmet по сравнению с тем же pug и все станет понятно - https://npmtrends.com/emmet-vs-pug

Препроцессоры позволяют более эффективно использовать возможности HTML/CSS и писать меньше кода. Прямой связи с анимационными фреймворками в данном случае нет. Относительно использования WebGL или аналогичных платформ - ограничения не позволяли нам использовать такого рода инструменты. Это было известно до начала разработки и поэтому воспринималось как архитектурное ограничение.

Это приложение разрабатывали 2 года назад. На тот момент сочетание указанных технологий было вполне обосновано. К тому же существовали внешние ограничения по выбору инструментов, о которых говориться в разделе 3 данной статьи

Уточним, что в этом примере идентификатор пользователя - GUID, поэтому атака SQL инъекцией невозможна

Спасибо за уточнение, речь о маппере, конечно же, поправили

Добрый день! Ответили выше на ваш комментарий, посмотрите, пожалуйста.
— В идеальном мире, конечно же, хочется видеть сразу актуальные данные на момент входа. В реальном мире можно использовать, например, систему push уведомлений, которая сообщит мобильному приложению, что счета на сервере обновились и их можно оперативно подгрузить.
— Что касается №3, нам бы это решение не подошло, как минимум из-за единой точки отказа.
NDA накладывает на нас определенные ограничения. Нам очень жаль, что мы не можем раскрыть подробности в достаточной степени для более детального обсуждения. Огромное спасибо за проявленный интерес и ваши комментарии. Желаем вам интересных проектов и вызовов в работе!
1. Таких кейсов не удалось вспомнить, поэтому подсказать, к сожалению, не можем.
2. Разные версии сервисов имеют разный API, соответственно МП работает со «своим» сервисом.
Здесь вопрос доработки таблиц базы данных для разных версий. В большинстве кейсов разные версии одного сервиса работают с одной таблицей. Есть кейсы, когда новый сервис будет использовать новые поля. Мы добавляем их в таблицу. Старый сервис продолжит только считывать данные и отправлять их пользователю. А новый сервис будет обновлять данные. Принцип немного похож на CQRS.
Как и обещали, мы уточнили эти вопросы с командой, постараемся ответить по пунктам.
1. Новый бэк пока используем только для нового мобильного приложения.
2. Мобильное приложение завязывается на определенный протокол взаимодействия с API сервера. На gateway указывается, какие версии сервисов соответствуют этому протоколу. Таким образом backend может релизить новые версии сервисов по их готовности, а когда мобилка, в свою очередь, готова с ними работать, мы выпускаем новый протокол.
3. Не совсем понятен вопрос, вы не могли бы конкретизировать?
4. Есть сервис, ответственный за данные клиента, который получает из АБС актуальные данные и хранит у себя в БД. При недоступности АБС данные могут браться из БД сервиса, если мы понимаем, что они актуальные (обновлялись недавно).
5. Есть несколько стендов, в том числе тест/предпрод. Такие ошибки выявляются на этапе тестирования.
6. К сожалению, не можем раскрыть эти подробности о проекте.
Спасибо за фидбек всем, кто читает и комментирует. Как отметили выше, комментарии подсветили ряд важных вопросов, которые мы не затрагивали в материале. Над проектом работала большая команда, уточним некоторые вопросы, чтобы описать подробнее. Просим простить за задержку, обязательно постараемся всем ответить)
Спасибо за ваши вопросы, уточним некоторые данные и вернемся с ответом.
Сразу отметим, что базы данных для микросервисов изолированы друг от друга, что позволяет конфигурировать серверы баз данных для обеспечения требуемой доступности и скорости работы. Просим извинить, если по тексту сложилось другое впечатление.
1

Information

Rating
967-th
Works in
Registered
Activity