Поддержать потоковый ответ на запрос ИИ (stream API)

tormozit Закрыто

Сейчас мы используем классический механизм запрос-ответ.
А большинство ИИ API и в частности Напарник поддерживают stream (потоковый) режим, т.е. один запрос и несколько ответов, чтобы быстрее доставлять первую часть результата. Каждая часть приходит с префиксом "data: ".
Предлагаю

  1. В обработчике ПриОтправкеЗапросаИИТ9 добавить флаг поддержки потокового режима
  2. При включенном фаге вызывать обработчик ПриПолученииОтветаИИТ9 при получении 2-й части (чанка) и остальных частей отдельно вместе с указанием флага первой части, т.е. будет 1-2 вызова ПриПолученииОтветаИИТ9 на один ПриОтправкеЗапросаИИТ9
  3. В обработчике ПриПолученииОтветаИИТ9 нужен флаг прерывания обработки ответа, т.к. я часто после первой порции знаю, что дальше нет смысла получать ответ.

Комментарии

tormozit
#1, ред. 11 октября 2025 12:04

Делаю это в ИР. Сложная штука, если прорабатывать тщательно, а выигрыш дает не такой большой, как я ожидал. В 2000мс ответе первая часть приходит всегда на второй секунде, т.е. в лучшем случае где то максимум на 50% быстрее появляется первая строка ответа ИИ.


tormozit
#2, 11 октября 2025 14:03

Сделал в ИР https://www.hostedredmine.com/issues/1007396


tormozit
#3, ред. 03 ноября 2025 08:58

Вот текст HTML документа, используемого в ИР для фоновых HTTP запросов. Думаю его можно кинуть в ИИ и получить заготовку для реализации в C#

Ссылка скрыта

Отправку делаю так

Процедура ОтправитьЗапрос(Знач Адрес, Знач ТелоЗапросаСтрока, Знач Заголовки, Знач ЛиПотоковый = Ложь, Знач ЛиПроверочный = Ложь, знач МинИнтервалОтветов = 500) Экспорт
	Если Не ирКэш.ДоступноБраузерWebKitЛкс() Тогда
		Возврат;
	КонецЕсли;
	мЛиПроверочный = ЛиПроверочный;
	ЗаголовкиМассив = Новый Массив;
	Для Каждого ЗаголовкиЭлемент Из Заголовки Цикл
		ЗаголовкиМассив.Добавить(Новый Структура("name,value", ЗаголовкиЭлемент.Ключ, ЗаголовкиЭлемент.Значение));
	КонецЦикла;
	ЗаголовкиТекст = ирОбщий.ОбъектВТекстЖСОНЛкс(ЗаголовкиМассив);
	мНакопленныйОтвет = "";
	мЛиПотоковый = ЛиПотоковый;
	Если мЛиПотоковый Тогда
		Браузер().sendStreamRequest(Адрес, ТелоЗапросаСтрока, ЗаголовкиТекст, МинИнтервалОтветов);
	Иначе
		Браузер().sendHttpRequest(Адрес, ТелоЗапросаСтрока, ЗаголовкиТекст);
	КонецЕсли;
КонецПроцедуры

Обработку события от HTML документа в 1С делаю так

Процедура СлужебноеПолеХТМЛonclick(Элемент, pEvtObj) Экспорт
	СтрокаСловаНапарника = СтрокаСловаНапарника();
	Если СтрокаСловаНапарника.Слово = СтатусИИНеЗнает() Тогда
		Возврат;
	КонецЕсли;
	ЛиПерваяЧасть = СтрокаСловаНапарника.Слово = СтатусИИДумает();
	ЛиПоследняяЧасть = мФоновыйЗапросХттп.ЛиПотокЗавершен();
	Если Истина
		И Не ЛиПерваяЧасть 
		И Не ЛиПоследняяЧасть
	Тогда
		Возврат;
	КонецЕсли;
	ЧастьОтвета = мФоновыйЗапросХттп.ПрочитатьЧастьОтвета();
	Если ЧастьОтвета = "" Тогда
		Возврат;
	КонецЕсли;
	Отказ = Ложь;
	Результат = ОбработатьОтветНапарника(ЧастьОтвета,, ЛиПерваяЧасть, ЛиПоследняяЧасть, Отказ);
	Если Отказ Тогда
		мФоновыйЗапросХттп.ОтменитьЗапрос();
		Если ПустаяСтрока(Результат) И ЛиПерваяЧасть Тогда
			Результат = СтатусИИНеЗнает();
		КонецЕсли;
		Если Не ЛиПоследняяЧасть Тогда
			// Чтобы отправились заказанные метаданные
			ОбработатьОтветНапарника("Конец:",, Ложь, Истина, Отказ);
		КонецЕсли;
	КонецЕсли;
	Если ЛиСжиматьПустыеСтроки() Тогда
		//УдалитьПустыеСтрокиВТекстеНапарника(Слово);
		Если ПустаяСтрока(Результат) Тогда
			Результат = "";
		КонецЕсли;
	КонецЕсли;
	Если СтрокаСловаНапарника.Слово = СтатусИИНеЗнает() Тогда
		// очень странная ситуация, как будто эти обработчики работают параллельно
		Возврат;
	КонецЕсли;
	Если СтрокаСловаНапарника.Слово <> СтатусИИДумает() Тогда
		Результат = СтрокаСловаНапарника.Слово + Результат;
	ИначеЕсли ЗначениеЗаполнено(Результат) Тогда
		Результат = СокрЛ(Результат);
	КонецЕсли;
	Если ПустаяСтрока(Результат) Тогда
		Результат = СтатусИИНеЗнает();
	КонецЕсли;
	СтрокаСловаНапарника.Слово = Результат;
	ЭлементыФормы.ТаблицаСлов.ОбновитьСтроки(ирОбщий.ЗначенияВМассивЛкс(СтрокаСловаНапарника));
	ОбновитьОписаниеСлова();
КонецПроцедуры

Общая функция обработки частей ответа Напарника для Турбоконфа и ИР

Функция ОбработатьОтветНапарника(Знач ТекстОтвета, ТекстОбновления = "", Знач ЛиПерваяЧасть = Истина, Отказ = Ложь) Экспорт
	Если ПустаяСтрока(ТекстОтвета) Тогда
		Возврат "";
	КонецЕсли; 
	ТекстОтвета = СокрЛ(ТекстОтвета);
	Если Истина
		И ирОбщий.СтрНачинаетсяСЛкс(ТекстОтвета, "Ошибка:")
		И ирОбщий.СтрКончаетсяНаЛкс(ТекстОтвета, "401") 
	Тогда
		СоединениеНапарника(, Истина);
		Возврат "";
	КонецЕсли;
	Слово = "";
	Отказ = Ложь;
	ПрефиксЧастиОтвета = "data: ";
	ЭлементыОбновления = Новый Массив;
	Если ирОбщий.СтрНачинаетсяСЛкс(ТекстОтвета, "Ошибка:") Тогда
		Слово = ТекстОтвета;
	ИначеЕсли ирОбщий.СтрНачинаетсяСЛкс(ТекстОтвета, "Отмена:") Тогда
		// Только для потокового режима
	ИначеЕсли ирОбщий.СтрНачинаетсяСЛкс(ТекстОтвета, "Конец:") Тогда
		// Только для потокового режима
	ИначеЕсли ирОбщий.СтрНачинаетсяСЛкс(ТекстОтвета, ПрефиксЧастиОтвета) Тогда
		...
	КонецЕсли;
	Возврат Слово;
КонецФункции


tormozit
#4, ред. 03 ноября 2025 09:02

Обкатал. Работает стабильно. В среднем дает ускорение отображения ответа Напарника на 30%.
Основные функции в Обработка.ирФоновыйЗапросХттп.Макет.ФоновыйHTTPЗапрос. Оттуда генерируется событие onclick о приходе новой порции ответа.


bolsun
#5, 07 ноября 2025 18:10

Сделано в 6.6
bolsun изменил статус на Закрыто


Для вставки изображения или файла, перетащите его в поле редактора или вставьте файл из буфера